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