xref: /aosp_15_r20/build/soong/cc/cmake_snapshot.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2024 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cc
16
17import (
18	"bytes"
19	_ "embed"
20	"fmt"
21	"path/filepath"
22	"slices"
23	"sort"
24	"strings"
25	"text/template"
26
27	"android/soong/android"
28
29	"github.com/google/blueprint"
30	"github.com/google/blueprint/proptools"
31)
32
33const veryVerbose bool = false
34
35//go:embed cmake_main.txt
36var templateCmakeMainRaw string
37var templateCmakeMain *template.Template = parseTemplate(templateCmakeMainRaw)
38
39//go:embed cmake_module_cc.txt
40var templateCmakeModuleCcRaw string
41var templateCmakeModuleCc *template.Template = parseTemplate(templateCmakeModuleCcRaw)
42
43//go:embed cmake_module_aidl.txt
44var templateCmakeModuleAidlRaw string
45var templateCmakeModuleAidl *template.Template = parseTemplate(templateCmakeModuleAidlRaw)
46
47//go:embed cmake_ext_add_aidl_library.txt
48var cmakeExtAddAidlLibrary string
49
50//go:embed cmake_ext_append_flags.txt
51var cmakeExtAppendFlags string
52
53var defaultUnportableFlags []string = []string{
54	"-Wno-c99-designator",
55	"-Wno-class-memaccess",
56	"-Wno-exit-time-destructors",
57	"-Winconsistent-missing-override",
58	"-Wno-inconsistent-missing-override",
59	"-Wreorder-init-list",
60	"-Wno-reorder-init-list",
61	"-Wno-restrict",
62	"-Wno-stringop-overread",
63	"-Wno-subobject-linkage",
64}
65
66var ignoredSystemLibs []string = []string{
67	"crtbegin_dynamic",
68	"crtend_android",
69	"libc",
70	"libc++",
71	"libc++_static",
72	"libc++demangle",
73	"libc_musl",
74	"libc_musl_crtbegin_so",
75	"libc_musl_crtbegin_static",
76	"libc_musl_crtend",
77	"libc_musl_crtend_so",
78	"libdl",
79	"libm",
80	"prebuilt_libclang_rt.builtins",
81	"prebuilt_libclang_rt.ubsan_minimal",
82}
83
84// Mapping entry between Android's library name and the one used when building outside Android tree.
85type LibraryMappingProperty struct {
86	// Android library name.
87	Android_name string
88
89	// Library name used when building outside Android.
90	Mapped_name string
91
92	// If the make file is already present in Android source tree, specify its location.
93	Package_pregenerated string
94
95	// If the package is expected to be installed on the build host OS, specify its name.
96	Package_system string
97}
98
99type CmakeSnapshotProperties struct {
100	// Host modules to add to the snapshot package. Their dependencies are pulled in automatically.
101	Modules_host []string
102
103	// System modules to add to the snapshot package. Their dependencies are pulled in automatically.
104	Modules_system []string
105
106	// Vendor modules to add to the snapshot package. Their dependencies are pulled in automatically.
107	Modules_vendor []string
108
109	// Host prebuilts to bundle with the snapshot. These are tools needed to build outside Android.
110	Prebuilts []string
111
112	// Global cflags to add when building outside Android.
113	Cflags []string
114
115	// Flags to skip when building outside Android.
116	Cflags_ignored []string
117
118	// Mapping between library names used in Android tree and externally.
119	Library_mapping []LibraryMappingProperty
120
121	// List of cflags that are not portable between compilers that could potentially be used to
122	// build a generated package. If left empty, it's initialized with a default list.
123	Unportable_flags []string
124
125	// Whether to include source code as part of the snapshot package.
126	Include_sources bool
127}
128
129var cmakeSnapshotSourcesProvider = blueprint.NewProvider[android.Paths]()
130
131type CmakeSnapshot struct {
132	android.ModuleBase
133
134	Properties CmakeSnapshotProperties
135
136	zipPath android.WritablePath
137}
138
139type cmakeProcessedProperties struct {
140	LibraryMapping       map[string]LibraryMappingProperty
141	PregeneratedPackages []string
142	SystemPackages       []string
143}
144
145type cmakeSnapshotDependencyTag struct {
146	blueprint.BaseDependencyTag
147	name string
148}
149
150var (
151	cmakeSnapshotModuleTag   = cmakeSnapshotDependencyTag{name: "cmake-snapshot-module"}
152	cmakeSnapshotPrebuiltTag = cmakeSnapshotDependencyTag{name: "cmake-snapshot-prebuilt"}
153)
154
155func parseTemplate(templateContents string) *template.Template {
156	funcMap := template.FuncMap{
157		"setList": func(name string, nameSuffix string, itemPrefix string, items []string) string {
158			var list strings.Builder
159			list.WriteString("set(" + name + nameSuffix)
160			templateListBuilder(&list, itemPrefix, items)
161			return list.String()
162		},
163		"toStrings": func(files android.Paths) []string {
164			return files.Strings()
165		},
166		"concat5": func(list1 []string, list2 []string, list3 []string, list4 []string, list5 []string) []string {
167			return append(append(append(append(list1, list2...), list3...), list4...), list5...)
168		},
169		"cflagsList": func(name string, nameSuffix string, flags []string,
170			unportableFlags []string, ignoredFlags []string) string {
171			if len(unportableFlags) == 0 {
172				unportableFlags = defaultUnportableFlags
173			}
174
175			var filteredPortable []string
176			var filteredUnportable []string
177			for _, flag := range flags {
178				if slices.Contains(ignoredFlags, flag) {
179					continue
180				} else if slices.Contains(unportableFlags, flag) {
181					filteredUnportable = append(filteredUnportable, flag)
182				} else {
183					filteredPortable = append(filteredPortable, flag)
184				}
185			}
186
187			var list strings.Builder
188
189			list.WriteString("set(" + name + nameSuffix)
190			templateListBuilder(&list, "", filteredPortable)
191
192			if len(filteredUnportable) > 0 {
193				list.WriteString("\nappend_cxx_flags_if_supported(" + name + nameSuffix)
194				templateListBuilder(&list, "", filteredUnportable)
195			}
196
197			return list.String()
198		},
199		"getSources": func(m *Module) android.Paths {
200			return m.compiler.(CompiledInterface).Srcs()
201		},
202		"getModuleType": getModuleType,
203		"getCompilerProperties": func(m *Module) BaseCompilerProperties {
204			return m.compiler.baseCompilerProps()
205		},
206		"getCflagsProperty": func(ctx android.ModuleContext, m *Module) []string {
207			prop := m.compiler.baseCompilerProps().Cflags
208			return prop.GetOrDefault(ctx, nil)
209		},
210		"getLinkerProperties": func(m *Module) BaseLinkerProperties {
211			return m.linker.baseLinkerProps()
212		},
213		"getWholeStaticLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
214			prop := m.linker.baseLinkerProps().Whole_static_libs
215			return prop.GetOrDefault(ctx, nil)
216		},
217		"getStaticLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
218			prop := m.linker.baseLinkerProps().Static_libs
219			return prop.GetOrDefault(ctx, nil)
220		},
221		"getSharedLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
222			prop := m.linker.baseLinkerProps().Shared_libs
223			return prop.GetOrDefault(ctx, nil)
224		},
225		"getHeaderLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
226			prop := m.linker.baseLinkerProps().Header_libs
227			return prop.GetOrDefault(ctx, nil)
228		},
229		"getExtraLibs":   getExtraLibs,
230		"getIncludeDirs": getIncludeDirs,
231		"mapLibraries": func(ctx android.ModuleContext, m *Module, libs []string, mapping map[string]LibraryMappingProperty) []string {
232			var mappedLibs []string
233			for _, lib := range libs {
234				mappedLib, exists := mapping[lib]
235				if exists {
236					lib = mappedLib.Mapped_name
237				} else {
238					if !ctx.OtherModuleExists(lib) {
239						ctx.OtherModuleErrorf(m, "Dependency %s doesn't exist", lib)
240					}
241					lib = "android::" + lib
242				}
243				if lib == "" {
244					continue
245				}
246				mappedLibs = append(mappedLibs, lib)
247			}
248			sort.Strings(mappedLibs)
249			mappedLibs = slices.Compact(mappedLibs)
250			return mappedLibs
251		},
252		"getAidlSources": func(m *Module) []string {
253			aidlInterface := m.compiler.baseCompilerProps().AidlInterface
254			aidlRoot := aidlInterface.AidlRoot + string(filepath.Separator)
255			if aidlInterface.AidlRoot == "" {
256				aidlRoot = ""
257			}
258			var sources []string
259			for _, src := range aidlInterface.Sources {
260				if !strings.HasPrefix(src, aidlRoot) {
261					panic(fmt.Sprintf("Aidl source '%v' doesn't start with '%v'", src, aidlRoot))
262				}
263				sources = append(sources, src[len(aidlRoot):])
264			}
265			return sources
266		},
267	}
268
269	return template.Must(template.New("").Delims("<<", ">>").Funcs(funcMap).Parse(templateContents))
270}
271
272func sliceWithPrefix(prefix string, slice []string) []string {
273	output := make([]string, len(slice))
274	for i, elem := range slice {
275		output[i] = prefix + elem
276	}
277	return output
278}
279
280func templateListBuilder(builder *strings.Builder, itemPrefix string, items []string) {
281	if len(items) > 0 {
282		builder.WriteString("\n")
283		for _, item := range items {
284			builder.WriteString("    " + itemPrefix + item + "\n")
285		}
286	}
287	builder.WriteString(")")
288}
289
290func executeTemplate(templ *template.Template, buffer *bytes.Buffer, data any) string {
291	buffer.Reset()
292	if err := templ.Execute(buffer, data); err != nil {
293		panic(err)
294	}
295	output := strings.TrimSpace(buffer.String())
296	buffer.Reset()
297	return output
298}
299
300func (m *CmakeSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) {
301	deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations()
302	deviceSystemVariations := append(deviceVariations, blueprint.Variation{"image", ""})
303	deviceVendorVariations := append(deviceVariations, blueprint.Variation{"image", "vendor"})
304	hostVariations := ctx.Config().BuildOSTarget.Variations()
305
306	ctx.AddVariationDependencies(hostVariations, cmakeSnapshotModuleTag, m.Properties.Modules_host...)
307	ctx.AddVariationDependencies(deviceSystemVariations, cmakeSnapshotModuleTag, m.Properties.Modules_system...)
308	ctx.AddVariationDependencies(deviceVendorVariations, cmakeSnapshotModuleTag, m.Properties.Modules_vendor...)
309
310	if len(m.Properties.Prebuilts) > 0 {
311		prebuilts := append(m.Properties.Prebuilts, "libc++")
312		ctx.AddVariationDependencies(hostVariations, cmakeSnapshotPrebuiltTag, prebuilts...)
313	}
314}
315
316func (m *CmakeSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
317	var templateBuffer bytes.Buffer
318	var pprop cmakeProcessedProperties
319	m.zipPath = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
320
321	// Process Library_mapping for more efficient lookups
322	pprop.LibraryMapping = map[string]LibraryMappingProperty{}
323	for _, elem := range m.Properties.Library_mapping {
324		pprop.LibraryMapping[elem.Android_name] = elem
325
326		if elem.Package_pregenerated != "" {
327			pprop.PregeneratedPackages = append(pprop.PregeneratedPackages, elem.Package_pregenerated)
328		}
329		sort.Strings(pprop.PregeneratedPackages)
330		pprop.PregeneratedPackages = slices.Compact(pprop.PregeneratedPackages)
331
332		if elem.Package_system != "" {
333			pprop.SystemPackages = append(pprop.SystemPackages, elem.Package_system)
334		}
335		sort.Strings(pprop.SystemPackages)
336		pprop.SystemPackages = slices.Compact(pprop.SystemPackages)
337	}
338
339	// Generating CMakeLists.txt rules for all modules in dependency tree
340	moduleDirs := map[string][]string{}
341	sourceFiles := map[string]android.Path{}
342	visitedModules := map[string]bool{}
343	var pregeneratedModules []*Module
344	ctx.WalkDeps(func(dep_a android.Module, parent android.Module) bool {
345		moduleName := ctx.OtherModuleName(dep_a)
346		if visited := visitedModules[moduleName]; visited {
347			return false // visit only once
348		}
349		visitedModules[moduleName] = true
350		dep, ok := dep_a.(*Module)
351		if !ok {
352			return false // not a cc module
353		}
354		if mapping, ok := pprop.LibraryMapping[moduleName]; ok {
355			if mapping.Package_pregenerated != "" {
356				pregeneratedModules = append(pregeneratedModules, dep)
357			}
358			return false // mapped to system or pregenerated (we'll handle these later)
359		}
360		if ctx.OtherModuleDependencyTag(dep) == cmakeSnapshotPrebuiltTag {
361			return false // we'll handle cmakeSnapshotPrebuiltTag later
362		}
363		if slices.Contains(ignoredSystemLibs, moduleName) {
364			return false // system libs built in-tree for Android
365		}
366		if dep.IsPrebuilt() {
367			return false // prebuilts are not supported
368		}
369		if dep.compiler == nil {
370			return false // unsupported module type
371		}
372		isAidlModule := dep.compiler.baseCompilerProps().AidlInterface.Lang != ""
373
374		if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
375			ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
376				"CMake snapshots not supported, despite being a dependency for %s",
377				ctx.OtherModuleName(parent))
378			return false
379		}
380
381		if veryVerbose {
382			fmt.Println("WalkDeps: " + ctx.OtherModuleName(parent) + " -> " + moduleName)
383		}
384
385		// Generate CMakeLists.txt fragment for this module
386		templateToUse := templateCmakeModuleCc
387		if isAidlModule {
388			templateToUse = templateCmakeModuleAidl
389		}
390		moduleFragment := executeTemplate(templateToUse, &templateBuffer, struct {
391			Ctx      *android.ModuleContext
392			M        *Module
393			Snapshot *CmakeSnapshot
394			Pprop    *cmakeProcessedProperties
395		}{
396			&ctx,
397			dep,
398			m,
399			&pprop,
400		})
401		moduleDir := ctx.OtherModuleDir(dep)
402		moduleDirs[moduleDir] = append(moduleDirs[moduleDir], moduleFragment)
403
404		if m.Properties.Include_sources {
405			files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider)
406			for _, file := range files {
407				sourceFiles[file.String()] = file
408			}
409		}
410
411		// if it's AIDL module, no need to dive into their dependencies
412		return !isAidlModule
413	})
414
415	// Enumerate sources for pregenerated modules
416	if m.Properties.Include_sources {
417		for _, dep := range pregeneratedModules {
418			if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
419				ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
420					"Pregenerated CMake snapshots not supported, despite being requested for %s",
421					ctx.ModuleName())
422				continue
423			}
424
425			files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider)
426			for _, file := range files {
427				sourceFiles[file.String()] = file
428			}
429		}
430	}
431
432	// Merging CMakeLists.txt contents for every module directory
433	var makefilesList android.Paths
434	for _, moduleDir := range android.SortedKeys(moduleDirs) {
435		fragments := moduleDirs[moduleDir]
436		moduleCmakePath := android.PathForModuleGen(ctx, moduleDir, "CMakeLists.txt")
437		makefilesList = append(makefilesList, moduleCmakePath)
438		sort.Strings(fragments)
439		android.WriteFileRule(ctx, moduleCmakePath, strings.Join(fragments, "\n\n\n"))
440	}
441
442	// Generating top-level CMakeLists.txt
443	mainCmakePath := android.PathForModuleGen(ctx, "CMakeLists.txt")
444	makefilesList = append(makefilesList, mainCmakePath)
445	mainContents := executeTemplate(templateCmakeMain, &templateBuffer, struct {
446		Ctx        *android.ModuleContext
447		M          *CmakeSnapshot
448		ModuleDirs map[string][]string
449		Pprop      *cmakeProcessedProperties
450	}{
451		&ctx,
452		m,
453		moduleDirs,
454		&pprop,
455	})
456	android.WriteFileRule(ctx, mainCmakePath, mainContents)
457
458	// Generating CMake extensions
459	extPath := android.PathForModuleGen(ctx, "cmake", "AppendCxxFlagsIfSupported.cmake")
460	makefilesList = append(makefilesList, extPath)
461	android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAppendFlags)
462	extPath = android.PathForModuleGen(ctx, "cmake", "AddAidlLibrary.cmake")
463	makefilesList = append(makefilesList, extPath)
464	android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAddAidlLibrary)
465
466	// Generating the final zip file
467	zipRule := android.NewRuleBuilder(pctx, ctx)
468	zipCmd := zipRule.Command().
469		BuiltTool("soong_zip").
470		FlagWithOutput("-o ", m.zipPath)
471
472	// Packaging all sources into the zip file
473	if m.Properties.Include_sources {
474		var sourcesList android.Paths
475		for _, file := range android.SortedKeys(sourceFiles) {
476			path := sourceFiles[file]
477			sourcesList = append(sourcesList, path)
478		}
479
480		sourcesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_sources.rsp")
481		zipCmd.FlagWithRspFileInputList("-r ", sourcesRspFile, sourcesList)
482	}
483
484	// Packaging all make files into the zip file
485	makefilesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_makefiles.rsp")
486	zipCmd.
487		FlagWithArg("-C ", android.PathForModuleGen(ctx).String()).
488		FlagWithRspFileInputList("-r ", makefilesRspFile, makefilesList)
489
490	// Packaging all prebuilts into the zip file
491	if len(m.Properties.Prebuilts) > 0 {
492		var prebuiltsList android.Paths
493
494		ctx.VisitDirectDepsWithTag(cmakeSnapshotPrebuiltTag, func(dep android.Module) {
495			for _, file := range android.OtherModuleProviderOrDefault(
496				ctx, dep, android.InstallFilesProvider).InstallFiles {
497				prebuiltsList = append(prebuiltsList, file)
498			}
499		})
500
501		prebuiltsRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_prebuilts.rsp")
502		zipCmd.
503			FlagWithArg("-C ", android.PathForArbitraryOutput(ctx).String()).
504			FlagWithArg("-P ", "prebuilts").
505			FlagWithRspFileInputList("-r ", prebuiltsRspFile, prebuiltsList)
506	}
507
508	// Finish generating the final zip file
509	zipRule.Build(m.zipPath.String(), "archiving "+ctx.ModuleName())
510
511	ctx.SetOutputFiles(android.Paths{m.zipPath}, "")
512}
513
514func (m *CmakeSnapshot) AndroidMkEntries() []android.AndroidMkEntries {
515	return []android.AndroidMkEntries{{
516		Class:      "DATA",
517		OutputFile: android.OptionalPathForPath(m.zipPath),
518		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
519			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
520				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
521			},
522		},
523	}}
524}
525
526func getModuleType(m *Module) string {
527	switch m.linker.(type) {
528	case *binaryDecorator:
529		return "executable"
530	case *libraryDecorator:
531		return "library"
532	case *testBinary:
533		return "test"
534	case *benchmarkDecorator:
535		return "test"
536	case *objectLinker:
537		return "object"
538	}
539	panic(fmt.Sprintf("Unexpected module type: %T", m.linker))
540}
541
542func getExtraLibs(m *Module) []string {
543	switch decorator := m.linker.(type) {
544	case *testBinary:
545		if decorator.testDecorator.gtest() {
546			return []string{
547				"libgtest",
548				"libgtest_main",
549			}
550		}
551	case *benchmarkDecorator:
552		return []string{"libgoogle-benchmark"}
553	}
554	return nil
555}
556
557func getIncludeDirs(ctx android.ModuleContext, m *Module) []string {
558	moduleDir := ctx.OtherModuleDir(m) + string(filepath.Separator)
559	switch decorator := m.compiler.(type) {
560	case *libraryDecorator:
561		return sliceWithPrefix(moduleDir, decorator.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil))
562	}
563	return nil
564}
565
566func cmakeSnapshotLoadHook(ctx android.LoadHookContext) {
567	props := struct {
568		Target struct {
569			Darwin struct {
570				Enabled *bool
571			}
572			Windows struct {
573				Enabled *bool
574			}
575		}
576	}{}
577	props.Target.Darwin.Enabled = proptools.BoolPtr(false)
578	props.Target.Windows.Enabled = proptools.BoolPtr(false)
579	ctx.AppendProperties(&props)
580}
581
582// cmake_snapshot allows defining source packages for release outside of Android build tree.
583// As a result of cmake_snapshot module build, a zip file is generated with CMake build definitions
584// for selected source modules, their dependencies and optionally also the source code itself.
585func CmakeSnapshotFactory() android.Module {
586	module := &CmakeSnapshot{}
587	module.AddProperties(&module.Properties)
588	android.AddLoadHook(module, cmakeSnapshotLoadHook)
589	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
590	return module
591}
592
593func init() {
594	android.InitRegistrationContext.RegisterModuleType("cc_cmake_snapshot", CmakeSnapshotFactory)
595}
596