2015-07-09 03:13:11 +02:00
|
|
|
// Copyright 2015 Google Inc. All rights reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// This file offers AndroidMkEntriesProvider, which individual modules implement to output
|
|
|
|
// Android.mk entries that contain information about the modules built through Soong. Kati reads
|
|
|
|
// and combines them with the legacy Make-based module definitions to produce the complete view of
|
|
|
|
// the source tree, which makes this a critical point of Make-Soong interoperability.
|
|
|
|
//
|
|
|
|
// Naturally, Soong-only builds do not rely on this mechanism.
|
|
|
|
|
2016-05-19 00:37:25 +02:00
|
|
|
package android
|
2015-07-09 03:13:11 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2016-02-10 02:43:51 +01:00
|
|
|
"fmt"
|
2015-07-09 03:13:11 +02:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2021-01-07 04:34:31 +01:00
|
|
|
"reflect"
|
2021-10-17 09:22:33 +02:00
|
|
|
"runtime"
|
2015-07-09 03:13:11 +02:00
|
|
|
"sort"
|
2023-11-06 22:54:06 +01:00
|
|
|
"strconv"
|
2016-06-02 00:25:32 +02:00
|
|
|
"strings"
|
2015-07-09 03:13:11 +02:00
|
|
|
|
|
|
|
"github.com/google/blueprint"
|
2018-09-28 19:19:18 +02:00
|
|
|
"github.com/google/blueprint/bootstrap"
|
2021-11-09 21:30:59 +01:00
|
|
|
"github.com/google/blueprint/pathtools"
|
2015-07-09 03:13:11 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2020-03-04 21:15:08 +01:00
|
|
|
RegisterAndroidMkBuildComponents(InitRegistrationContext)
|
|
|
|
}
|
|
|
|
|
|
|
|
func RegisterAndroidMkBuildComponents(ctx RegistrationContext) {
|
2023-05-16 02:58:37 +02:00
|
|
|
ctx.RegisterParallelSingletonType("androidmk", AndroidMkSingleton)
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
2021-03-07 16:44:41 +01:00
|
|
|
// Enable androidmk support.
|
|
|
|
// * Register the singleton
|
|
|
|
// * Configure that we are inside make
|
|
|
|
var PrepareForTestWithAndroidMk = GroupFixturePreparers(
|
|
|
|
FixtureRegisterWithContext(RegisterAndroidMkBuildComponents),
|
|
|
|
FixtureModifyConfig(SetKatiEnabledForTests),
|
|
|
|
)
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// Deprecated: Use AndroidMkEntriesProvider instead, especially if you're not going to use the
|
|
|
|
// Custom function. It's easier to use and test.
|
2015-07-09 03:13:11 +02:00
|
|
|
type AndroidMkDataProvider interface {
|
2017-08-11 02:00:19 +02:00
|
|
|
AndroidMk() AndroidMkData
|
2016-10-07 01:12:58 +02:00
|
|
|
BaseModuleName() string
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type AndroidMkData struct {
|
2019-04-02 03:37:36 +02:00
|
|
|
Class string
|
|
|
|
SubName string
|
2020-06-15 07:24:19 +02:00
|
|
|
DistFiles TaggedDistFiles
|
2019-04-02 03:37:36 +02:00
|
|
|
OutputFile OptionalPath
|
|
|
|
Disabled bool
|
|
|
|
Include string
|
|
|
|
Required []string
|
|
|
|
Host_required []string
|
|
|
|
Target_required []string
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2017-08-11 02:07:28 +02:00
|
|
|
Custom func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData)
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2017-08-11 01:32:23 +02:00
|
|
|
Extra []AndroidMkExtraFunc
|
2017-08-11 02:07:28 +02:00
|
|
|
|
2020-06-24 16:26:26 +02:00
|
|
|
Entries AndroidMkEntries
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
2017-08-11 01:32:23 +02:00
|
|
|
type AndroidMkExtraFunc func(w io.Writer, outputFile Path)
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// Interface for modules to declare their Android.mk outputs. Note that every module needs to
|
|
|
|
// implement this in order to be included in the final Android-<product_name>.mk output, even if
|
|
|
|
// they only need to output the common set of entries without any customizations.
|
2019-04-04 00:47:29 +02:00
|
|
|
type AndroidMkEntriesProvider interface {
|
2020-11-16 21:50:29 +01:00
|
|
|
// Returns AndroidMkEntries objects that contain all basic info plus extra customization data
|
|
|
|
// if needed. This is the core func to implement.
|
|
|
|
// Note that one can return multiple objects. For example, java_library may return an additional
|
|
|
|
// AndroidMkEntries object for its hostdex sub-module.
|
2019-12-03 05:24:29 +01:00
|
|
|
AndroidMkEntries() []AndroidMkEntries
|
2020-11-16 21:50:29 +01:00
|
|
|
// Modules don't need to implement this as it's already implemented by ModuleBase.
|
|
|
|
// AndroidMkEntries uses BaseModuleName() instead of ModuleName() because certain modules
|
|
|
|
// e.g. Prebuilts, override the Name() func and return modified names.
|
|
|
|
// If a different name is preferred, use SubName or OverrideName in AndroidMkEntries.
|
2019-04-04 00:47:29 +02:00
|
|
|
BaseModuleName() string
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// The core data struct that modules use to provide their Android.mk data.
|
2019-04-04 00:47:29 +02:00
|
|
|
type AndroidMkEntries struct {
|
2020-11-16 21:50:29 +01:00
|
|
|
// Android.mk class string, e.g EXECUTABLES, JAVA_LIBRARIES, ETC
|
|
|
|
Class string
|
|
|
|
// Optional suffix to append to the module name. Useful when a module wants to return multiple
|
|
|
|
// AndroidMkEntries objects. For example, when a java_library returns an additional entry for
|
|
|
|
// its hostdex sub-module, this SubName field is set to "-hostdex" so that it can have a
|
|
|
|
// different name than the parent's.
|
|
|
|
SubName string
|
|
|
|
// If set, this value overrides the base module name. SubName is still appended.
|
|
|
|
OverrideName string
|
|
|
|
// Dist files to output
|
|
|
|
DistFiles TaggedDistFiles
|
|
|
|
// The output file for Kati to process and/or install. If absent, the module is skipped.
|
|
|
|
OutputFile OptionalPath
|
|
|
|
// If true, the module is skipped and does not appear on the final Android-<product name>.mk
|
|
|
|
// file. Useful when a module needs to be skipped conditionally.
|
|
|
|
Disabled bool
|
2021-11-12 19:27:58 +01:00
|
|
|
// The postprocessing mk file to include, e.g. $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk
|
2020-11-16 21:50:29 +01:00
|
|
|
// If not set, $(BUILD_SYSTEM)/prebuilt.mk is used.
|
|
|
|
Include string
|
|
|
|
// Required modules that need to be built and included in the final build output when building
|
|
|
|
// this module.
|
|
|
|
Required []string
|
|
|
|
// Required host modules that need to be built and included in the final build output when
|
|
|
|
// building this module.
|
|
|
|
Host_required []string
|
|
|
|
// Required device modules that need to be built and included in the final build output when
|
|
|
|
// building this module.
|
2019-04-04 00:47:29 +02:00
|
|
|
Target_required []string
|
|
|
|
|
|
|
|
header bytes.Buffer
|
|
|
|
footer bytes.Buffer
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// Funcs to append additional Android.mk entries or modify the common ones. Multiple funcs are
|
|
|
|
// accepted so that common logic can be factored out as a shared func.
|
2019-08-28 02:33:16 +02:00
|
|
|
ExtraEntries []AndroidMkExtraEntriesFunc
|
2020-11-16 21:50:29 +01:00
|
|
|
// Funcs to add extra lines to the module's Android.mk output. Unlike AndroidMkExtraEntriesFunc,
|
|
|
|
// which simply sets Make variable values, this can be used for anything since it can write any
|
|
|
|
// Make statements directly to the final Android-*.mk file.
|
|
|
|
// Primarily used to call macros or declare/update Make targets.
|
2019-08-29 23:56:03 +02:00
|
|
|
ExtraFooters []AndroidMkExtraFootersFunc
|
2019-04-04 00:47:29 +02:00
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// A map that holds the up-to-date Make variable values. Can be accessed from tests.
|
|
|
|
EntryMap map[string][]string
|
|
|
|
// A list of EntryMap keys in insertion order. This serves a few purposes:
|
|
|
|
// 1. Prevents churns. Golang map doesn't provide consistent iteration order, so without this,
|
|
|
|
// the outputted Android-*.mk file may change even though there have been no content changes.
|
|
|
|
// 2. Allows modules to refer to other variables, like LOCAL_BAR_VAR := $(LOCAL_FOO_VAR),
|
|
|
|
// without worrying about the variables being mixed up in the actual mk file.
|
|
|
|
// 3. Makes troubleshooting and spotting errors easier.
|
2019-04-04 00:47:29 +02:00
|
|
|
entryOrder []string
|
2022-03-21 20:34:02 +01:00
|
|
|
|
|
|
|
// Provides data typically stored by Context objects that are commonly needed by
|
|
|
|
//AndroidMkEntries objects.
|
|
|
|
entryContext AndroidMkEntriesContext
|
|
|
|
}
|
|
|
|
|
|
|
|
type AndroidMkEntriesContext interface {
|
|
|
|
Config() Config
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
2020-07-03 22:18:24 +02:00
|
|
|
type AndroidMkExtraEntriesContext interface {
|
2023-12-12 23:13:26 +01:00
|
|
|
Provider(provider blueprint.AnyProviderKey) (any, bool)
|
2020-07-03 22:18:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type androidMkExtraEntriesContext struct {
|
|
|
|
ctx fillInEntriesContext
|
|
|
|
mod blueprint.Module
|
|
|
|
}
|
|
|
|
|
2023-12-12 23:13:26 +01:00
|
|
|
func (a *androidMkExtraEntriesContext) Provider(provider blueprint.AnyProviderKey) (any, bool) {
|
|
|
|
return a.ctx.moduleProvider(a.mod, provider)
|
2020-07-03 22:18:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type AndroidMkExtraEntriesFunc func(ctx AndroidMkExtraEntriesContext, entries *AndroidMkEntries)
|
2020-12-07 19:23:54 +01:00
|
|
|
type AndroidMkExtraFootersFunc func(w io.Writer, name, prefix, moduleDir string)
|
2019-08-28 02:33:16 +02:00
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// Utility funcs to manipulate Android.mk variable entries.
|
|
|
|
|
|
|
|
// SetString sets a Make variable with the given name to the given value.
|
2019-04-04 00:47:29 +02:00
|
|
|
func (a *AndroidMkEntries) SetString(name, value string) {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = []string{value}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// SetPath sets a Make variable with the given name to the given path string.
|
2019-09-05 05:17:54 +02:00
|
|
|
func (a *AndroidMkEntries) SetPath(name string, path Path) {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = []string{path.String()}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// SetOptionalPath sets a Make variable with the given name to the given path string if it is valid.
|
|
|
|
// It is a no-op if the given path is invalid.
|
2020-07-03 20:56:24 +02:00
|
|
|
func (a *AndroidMkEntries) SetOptionalPath(name string, path OptionalPath) {
|
|
|
|
if path.Valid() {
|
|
|
|
a.SetPath(name, path.Path())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// AddPath appends the given path string to a Make variable with the given name.
|
2020-07-03 20:56:24 +02:00
|
|
|
func (a *AndroidMkEntries) AddPath(name string, path Path) {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = append(a.EntryMap[name], path.String())
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// AddOptionalPath appends the given path string to a Make variable with the given name if it is
|
|
|
|
// valid. It is a no-op if the given path is invalid.
|
2020-07-03 20:56:24 +02:00
|
|
|
func (a *AndroidMkEntries) AddOptionalPath(name string, path OptionalPath) {
|
|
|
|
if path.Valid() {
|
|
|
|
a.AddPath(name, path.Path())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// SetPaths sets a Make variable with the given name to a slice of the given path strings.
|
2020-07-22 05:31:17 +02:00
|
|
|
func (a *AndroidMkEntries) SetPaths(name string, paths Paths) {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = paths.Strings()
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// SetOptionalPaths sets a Make variable with the given name to a slice of the given path strings
|
|
|
|
// only if there are a non-zero amount of paths.
|
2020-07-22 05:31:17 +02:00
|
|
|
func (a *AndroidMkEntries) SetOptionalPaths(name string, paths Paths) {
|
|
|
|
if len(paths) > 0 {
|
|
|
|
a.SetPaths(name, paths)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// AddPaths appends the given path strings to a Make variable with the given name.
|
2020-07-22 05:31:17 +02:00
|
|
|
func (a *AndroidMkEntries) AddPaths(name string, paths Paths) {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = append(a.EntryMap[name], paths.Strings()...)
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// SetBoolIfTrue sets a Make variable with the given name to true if the given flag is true.
|
|
|
|
// It is a no-op if the given flag is false.
|
2019-04-04 00:47:29 +02:00
|
|
|
func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
|
|
|
|
if flag {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = []string{"true"}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// SetBool sets a Make variable with the given name to if the given bool flag value.
|
2019-09-05 05:17:54 +02:00
|
|
|
func (a *AndroidMkEntries) SetBool(name string, flag bool) {
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
if flag {
|
|
|
|
a.EntryMap[name] = []string{"true"}
|
|
|
|
} else {
|
|
|
|
a.EntryMap[name] = []string{"false"}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// AddStrings appends the given strings to a Make variable with the given name.
|
2019-04-04 00:47:29 +02:00
|
|
|
func (a *AndroidMkEntries) AddStrings(name string, value ...string) {
|
|
|
|
if len(value) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if _, ok := a.EntryMap[name]; !ok {
|
|
|
|
a.entryOrder = append(a.entryOrder, name)
|
|
|
|
}
|
|
|
|
a.EntryMap[name] = append(a.EntryMap[name], value...)
|
|
|
|
}
|
|
|
|
|
2020-11-24 21:42:58 +01:00
|
|
|
// AddCompatibilityTestSuites adds the supplied test suites to the EntryMap, with special handling
|
2024-01-04 10:03:35 +01:00
|
|
|
// for partial MTS and MCTS test suites.
|
2020-11-24 21:42:58 +01:00
|
|
|
func (a *AndroidMkEntries) AddCompatibilityTestSuites(suites ...string) {
|
2024-01-04 10:03:35 +01:00
|
|
|
// M(C)TS supports a full test suite and partial per-module MTS test suites, with naming mts-${MODULE}.
|
|
|
|
// To reduce repetition, if we find a partial M(C)TS test suite without an full M(C)TS test suite,
|
2020-11-24 21:42:58 +01:00
|
|
|
// we add the full test suite to our list.
|
|
|
|
if PrefixInList(suites, "mts-") && !InList("mts", suites) {
|
|
|
|
suites = append(suites, "mts")
|
|
|
|
}
|
2024-01-04 10:03:35 +01:00
|
|
|
if PrefixInList(suites, "mcts-") && !InList("mcts", suites) {
|
|
|
|
suites = append(suites, "mcts")
|
|
|
|
}
|
2020-11-24 21:42:58 +01:00
|
|
|
a.AddStrings("LOCAL_COMPATIBILITY_SUITE", suites...)
|
|
|
|
}
|
|
|
|
|
2020-11-26 15:33:21 +01:00
|
|
|
// The contributions to the dist.
|
|
|
|
type distContributions struct {
|
2022-04-13 20:27:19 +02:00
|
|
|
// Path to license metadata file.
|
|
|
|
licenseMetadataFile Path
|
2020-11-26 15:33:21 +01:00
|
|
|
// List of goals and the dist copy instructions.
|
|
|
|
copiesForGoals []*copiesForGoals
|
|
|
|
}
|
|
|
|
|
|
|
|
// getCopiesForGoals returns a copiesForGoals into which copy instructions that
|
|
|
|
// must be processed when building one or more of those goals can be added.
|
|
|
|
func (d *distContributions) getCopiesForGoals(goals string) *copiesForGoals {
|
|
|
|
copiesForGoals := &copiesForGoals{goals: goals}
|
|
|
|
d.copiesForGoals = append(d.copiesForGoals, copiesForGoals)
|
|
|
|
return copiesForGoals
|
|
|
|
}
|
|
|
|
|
|
|
|
// Associates a list of dist copy instructions with a set of goals for which they
|
|
|
|
// should be run.
|
|
|
|
type copiesForGoals struct {
|
|
|
|
// goals are a space separated list of build targets that will trigger the
|
|
|
|
// copy instructions.
|
|
|
|
goals string
|
|
|
|
|
|
|
|
// A list of instructions to copy a module's output files to somewhere in the
|
|
|
|
// dist directory.
|
|
|
|
copies []distCopy
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adds a copy instruction.
|
|
|
|
func (d *copiesForGoals) addCopyInstruction(from Path, dest string) {
|
|
|
|
d.copies = append(d.copies, distCopy{from, dest})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instruction on a path that must be copied into the dist.
|
|
|
|
type distCopy struct {
|
|
|
|
// The path to copy from.
|
|
|
|
from Path
|
|
|
|
|
|
|
|
// The destination within the dist directory to copy to.
|
|
|
|
dest string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute the contributions that the module makes to the dist.
|
|
|
|
func (a *AndroidMkEntries) getDistContributions(mod blueprint.Module) *distContributions {
|
2019-04-04 00:47:29 +02:00
|
|
|
amod := mod.(Module).base()
|
|
|
|
name := amod.BaseModuleName()
|
|
|
|
|
2020-11-25 17:37:46 +01:00
|
|
|
// Collate the set of associated tag/paths available for copying to the dist.
|
|
|
|
// Start with an empty (nil) set.
|
2020-07-24 11:13:49 +02:00
|
|
|
var availableTaggedDists TaggedDistFiles
|
2020-06-15 07:24:19 +02:00
|
|
|
|
2020-11-25 17:37:46 +01:00
|
|
|
// Then merge in any that are provided explicitly by the module.
|
2020-07-21 13:32:19 +02:00
|
|
|
if a.DistFiles != nil {
|
2020-11-25 17:37:46 +01:00
|
|
|
// Merge the DistFiles into the set.
|
|
|
|
availableTaggedDists = availableTaggedDists.merge(a.DistFiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no paths have been provided for the DefaultDistTag and the output file is
|
|
|
|
// valid then add that as the default dist path.
|
|
|
|
if _, ok := availableTaggedDists[DefaultDistTag]; !ok && a.OutputFile.Valid() {
|
|
|
|
availableTaggedDists = availableTaggedDists.addPathsForTag(DefaultDistTag, a.OutputFile.Path())
|
|
|
|
}
|
|
|
|
|
2020-11-24 00:32:56 +01:00
|
|
|
// If the distFiles created by GenerateTaggedDistFiles contains paths for the
|
|
|
|
// DefaultDistTag then that takes priority so delete any existing paths.
|
|
|
|
if _, ok := amod.distFiles[DefaultDistTag]; ok {
|
|
|
|
delete(availableTaggedDists, DefaultDistTag)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, merge the distFiles created by GenerateTaggedDistFiles.
|
|
|
|
availableTaggedDists = availableTaggedDists.merge(amod.distFiles)
|
|
|
|
|
2020-11-25 17:37:46 +01:00
|
|
|
if len(availableTaggedDists) == 0 {
|
2020-07-24 11:13:49 +02:00
|
|
|
// Nothing dist-able for this module.
|
|
|
|
return nil
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
2020-11-26 15:33:21 +01:00
|
|
|
// Collate the contributions this module makes to the dist.
|
|
|
|
distContributions := &distContributions{}
|
|
|
|
|
2022-09-13 01:06:03 +02:00
|
|
|
if !exemptFromRequiredApplicableLicensesProperty(mod.(Module)) {
|
|
|
|
distContributions.licenseMetadataFile = amod.licenseMetadataFile
|
|
|
|
}
|
2022-04-13 20:27:19 +02:00
|
|
|
|
2020-06-15 07:24:19 +02:00
|
|
|
// Iterate over this module's dist structs, merged from the dist and dists properties.
|
|
|
|
for _, dist := range amod.Dists() {
|
|
|
|
// Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
|
|
|
|
goals := strings.Join(dist.Targets, " ")
|
|
|
|
|
|
|
|
// Get the tag representing the output files to be dist'd. e.g. ".jar", ".proguard_map"
|
|
|
|
var tag string
|
|
|
|
if dist.Tag == nil {
|
|
|
|
// If the dist struct does not specify a tag, use the default output files tag.
|
2020-11-25 17:37:46 +01:00
|
|
|
tag = DefaultDistTag
|
2020-06-15 07:24:19 +02:00
|
|
|
} else {
|
|
|
|
tag = *dist.Tag
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 07:24:19 +02:00
|
|
|
// Get the paths of the output files to be dist'd, represented by the tag.
|
|
|
|
// Can be an empty list.
|
|
|
|
tagPaths := availableTaggedDists[tag]
|
|
|
|
if len(tagPaths) == 0 {
|
|
|
|
// Nothing to dist for this tag, continue to the next dist.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
|
2020-11-25 17:37:46 +01:00
|
|
|
errorMessage := "%s: Cannot apply dest/suffix for more than one dist " +
|
|
|
|
"file for %q goals tag %q in module %s. The list of dist files, " +
|
2020-06-15 07:24:19 +02:00
|
|
|
"which should have a single element, is:\n%s"
|
2020-11-25 17:37:46 +01:00
|
|
|
panic(fmt.Errorf(errorMessage, mod, goals, tag, name, tagPaths))
|
2020-06-15 07:24:19 +02:00
|
|
|
}
|
|
|
|
|
2020-11-26 15:33:21 +01:00
|
|
|
copiesForGoals := distContributions.getCopiesForGoals(goals)
|
2020-06-15 07:24:19 +02:00
|
|
|
|
2020-11-26 15:33:21 +01:00
|
|
|
// Iterate over each path adding a copy instruction to copiesForGoals
|
2020-06-15 07:24:19 +02:00
|
|
|
for _, path := range tagPaths {
|
|
|
|
// It's possible that the Path is nil from errant modules. Be defensive here.
|
|
|
|
if path == nil {
|
|
|
|
tagName := "default" // for error message readability
|
|
|
|
if dist.Tag != nil {
|
|
|
|
tagName = *dist.Tag
|
|
|
|
}
|
|
|
|
panic(fmt.Errorf("Dist file should not be nil for the %s tag in %s", tagName, name))
|
|
|
|
}
|
|
|
|
|
|
|
|
dest := filepath.Base(path.String())
|
|
|
|
|
|
|
|
if dist.Dest != nil {
|
2019-04-04 00:47:29 +02:00
|
|
|
var err error
|
2020-06-15 07:24:19 +02:00
|
|
|
if dest, err = validateSafePath(*dist.Dest); err != nil {
|
2019-04-04 00:47:29 +02:00
|
|
|
// This was checked in ModuleBase.GenerateBuildActions
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-21 20:34:02 +01:00
|
|
|
ext := filepath.Ext(dest)
|
|
|
|
suffix := ""
|
2020-06-15 07:24:19 +02:00
|
|
|
if dist.Suffix != nil {
|
2022-03-21 20:34:02 +01:00
|
|
|
suffix = *dist.Suffix
|
|
|
|
}
|
|
|
|
|
|
|
|
productString := ""
|
|
|
|
if dist.Append_artifact_with_product != nil && *dist.Append_artifact_with_product {
|
|
|
|
productString = fmt.Sprintf("_%s", a.entryContext.Config().DeviceProduct())
|
|
|
|
}
|
|
|
|
|
|
|
|
if suffix != "" || productString != "" {
|
|
|
|
dest = strings.TrimSuffix(dest, ext) + suffix + productString + ext
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 07:24:19 +02:00
|
|
|
if dist.Dir != nil {
|
2019-04-04 00:47:29 +02:00
|
|
|
var err error
|
2020-06-15 07:24:19 +02:00
|
|
|
if dest, err = validateSafePath(*dist.Dir, dest); err != nil {
|
2019-04-04 00:47:29 +02:00
|
|
|
// This was checked in ModuleBase.GenerateBuildActions
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-26 15:33:21 +01:00
|
|
|
copiesForGoals.addCopyInstruction(path, dest)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return distContributions
|
|
|
|
}
|
|
|
|
|
|
|
|
// generateDistContributionsForMake generates make rules that will generate the
|
|
|
|
// dist according to the instructions in the supplied distContribution.
|
|
|
|
func generateDistContributionsForMake(distContributions *distContributions) []string {
|
|
|
|
var ret []string
|
|
|
|
for _, d := range distContributions.copiesForGoals {
|
|
|
|
ret = append(ret, fmt.Sprintf(".PHONY: %s\n", d.goals))
|
|
|
|
// Create dist-for-goals calls for each of the copy instructions.
|
|
|
|
for _, c := range d.copies {
|
2022-09-13 01:06:03 +02:00
|
|
|
if distContributions.licenseMetadataFile != nil {
|
|
|
|
ret = append(
|
|
|
|
ret,
|
|
|
|
fmt.Sprintf("$(if $(strip $(ALL_TARGETS.%s.META_LIC)),,$(eval ALL_TARGETS.%s.META_LIC := %s))\n",
|
|
|
|
c.from.String(), c.from.String(), distContributions.licenseMetadataFile.String()))
|
|
|
|
}
|
2020-06-15 07:24:19 +02:00
|
|
|
ret = append(
|
|
|
|
ret,
|
2020-11-26 15:33:21 +01:00
|
|
|
fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", d.goals, c.from.String(), c.dest))
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-15 07:24:19 +02:00
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2020-11-26 15:33:21 +01:00
|
|
|
// Compute the list of Make strings to declare phony goals and dist-for-goals
|
|
|
|
// calls from the module's dist and dists properties.
|
|
|
|
func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
|
|
|
|
distContributions := a.getDistContributions(mod)
|
|
|
|
if distContributions == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return generateDistContributionsForMake(distContributions)
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// fillInEntries goes through the common variable processing and calls the extra data funcs to
|
|
|
|
// generate and fill in AndroidMkEntries's in-struct data, ready to be flushed to a file.
|
2020-07-03 22:18:24 +02:00
|
|
|
type fillInEntriesContext interface {
|
|
|
|
ModuleDir(module blueprint.Module) string
|
2023-02-24 01:57:43 +01:00
|
|
|
ModuleSubDir(module blueprint.Module) string
|
2020-07-03 22:18:24 +02:00
|
|
|
Config() Config
|
2023-12-12 23:13:26 +01:00
|
|
|
moduleProvider(module blueprint.Module, provider blueprint.AnyProviderKey) (any, bool)
|
2022-12-01 19:49:23 +01:00
|
|
|
ModuleType(module blueprint.Module) string
|
2020-07-03 22:18:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *AndroidMkEntries) fillInEntries(ctx fillInEntriesContext, mod blueprint.Module) {
|
2022-03-21 20:34:02 +01:00
|
|
|
a.entryContext = ctx
|
2020-06-15 07:24:19 +02:00
|
|
|
a.EntryMap = make(map[string][]string)
|
2021-10-22 01:14:19 +02:00
|
|
|
amod := mod.(Module)
|
|
|
|
base := amod.base()
|
|
|
|
name := base.BaseModuleName()
|
2020-10-14 03:43:54 +02:00
|
|
|
if a.OverrideName != "" {
|
|
|
|
name = a.OverrideName
|
|
|
|
}
|
2020-06-15 07:24:19 +02:00
|
|
|
|
|
|
|
if a.Include == "" {
|
|
|
|
a.Include = "$(BUILD_PREBUILT)"
|
|
|
|
}
|
2021-10-22 01:14:19 +02:00
|
|
|
a.Required = append(a.Required, amod.RequiredModuleNames()...)
|
|
|
|
a.Host_required = append(a.Host_required, amod.HostRequiredModuleNames()...)
|
|
|
|
a.Target_required = append(a.Target_required, amod.TargetRequiredModuleNames()...)
|
2020-06-15 07:24:19 +02:00
|
|
|
|
|
|
|
for _, distString := range a.GetDistForGoals(mod) {
|
|
|
|
fmt.Fprintf(&a.header, distString)
|
|
|
|
}
|
|
|
|
|
2023-02-24 01:57:43 +01:00
|
|
|
fmt.Fprintf(&a.header, "\ninclude $(CLEAR_VARS) # type: %s, name: %s, variant: %s\n", ctx.ModuleType(mod), base.BaseModuleName(), ctx.ModuleSubDir(mod))
|
2019-04-04 00:47:29 +02:00
|
|
|
|
|
|
|
// Collect make variable assignment entries.
|
2020-07-03 22:18:24 +02:00
|
|
|
a.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
|
2019-04-04 00:47:29 +02:00
|
|
|
a.SetString("LOCAL_MODULE", name+a.SubName)
|
|
|
|
a.SetString("LOCAL_MODULE_CLASS", a.Class)
|
|
|
|
a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String())
|
|
|
|
a.AddStrings("LOCAL_REQUIRED_MODULES", a.Required...)
|
|
|
|
a.AddStrings("LOCAL_HOST_REQUIRED_MODULES", a.Host_required...)
|
|
|
|
a.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", a.Target_required...)
|
2023-01-05 02:12:24 +01:00
|
|
|
a.AddStrings("LOCAL_SOONG_MODULE_TYPE", ctx.ModuleType(amod))
|
2019-04-04 00:47:29 +02:00
|
|
|
|
2021-09-29 02:40:21 +02:00
|
|
|
// If the install rule was generated by Soong tell Make about it.
|
2021-11-12 03:59:15 +01:00
|
|
|
if len(base.katiInstalls) > 0 {
|
2021-09-29 02:40:21 +02:00
|
|
|
// Assume the primary install file is last since it probably needs to depend on any other
|
|
|
|
// installed files. If that is not the case we can add a method to specify the primary
|
|
|
|
// installed file.
|
|
|
|
a.SetPath("LOCAL_SOONG_INSTALLED_MODULE", base.katiInstalls[len(base.katiInstalls)-1].to)
|
|
|
|
a.SetString("LOCAL_SOONG_INSTALL_PAIRS", base.katiInstalls.BuiltInstalled())
|
|
|
|
a.SetPaths("LOCAL_SOONG_INSTALL_SYMLINKS", base.katiSymlinks.InstallPaths().Paths())
|
|
|
|
}
|
|
|
|
|
2023-11-15 21:39:40 +01:00
|
|
|
if len(base.testData) > 0 {
|
|
|
|
a.AddStrings("LOCAL_TEST_DATA", androidMkDataPaths(base.testData)...)
|
|
|
|
}
|
|
|
|
|
mark platform un-availability
A module is marked unavailable for platform when 1) it does not have
"//apex_available:platform" in its apex_available property, or 2)
it depends on another module that is unavailable for platform.
In that case, LOCAL_NOT_AVAILABLE_FOR_PLATFORM is set to true for the
module in the Make world. Later, that flag is used to ensure that there
is no module with the flag is installed to the device.
The reason why this isn't entirely done in Soong is because Soong
doesn't know if a module will be installed to the device or not. To
explain this, let's have an example.
cc_test { name: "mytest", static_libs: ["libfoo"]}
cc_library_static { name: "libfoo", static_libs: ["libbar"]}
cc_library { name: "libbar", apex_available: ["com.android.xxx"]}
Here, libbar is not available for platform, but is used by libfoo which
is available for platform (apex_available defaults to
"//apex_available:platform"). libfoo is again depended on by mytest
which again is available for platform. The use of libbar should be
allowed in the context of test; we don't want to make libbar available
to platform just for the dependency from test because it will allow
non-test uses of the library as well.
Soong by itself can't tell whether libfoo and libbar are used only in the
context of a test. There could be another module depending them, e.g.,
cc_library_shared { name: "mylib", static_libs: ["libfoo"] }
can exist and it might be installed to the device, in which case
we really should trigger an error.
Since Make has the knowledge of what's installed and what's not,
the check should be done there.
Bug: 153073816
Test: m
Test: remove "//apex_available:platform" from libmdnssd (it is currently
installed to /system/lib), and check that `m system_image` fails
Change-Id: Ia304cc5f41f173229e8a154e90cea4dce46dcebe
2020-04-07 09:37:39 +02:00
|
|
|
if am, ok := mod.(ApexModule); ok {
|
|
|
|
a.SetBoolIfTrue("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", am.NotAvailableForPlatform())
|
|
|
|
}
|
|
|
|
|
2021-10-22 01:14:19 +02:00
|
|
|
archStr := base.Arch().ArchType.String()
|
2019-04-04 00:47:29 +02:00
|
|
|
host := false
|
2021-10-22 01:14:19 +02:00
|
|
|
switch base.Os().Class {
|
2019-04-04 00:47:29 +02:00
|
|
|
case Host:
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.Target().HostCross {
|
2020-09-14 12:43:17 +02:00
|
|
|
// Make cannot identify LOCAL_MODULE_HOST_CROSS_ARCH:= common.
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.Arch().ArchType != Common {
|
2020-09-14 12:43:17 +02:00
|
|
|
a.SetString("LOCAL_MODULE_HOST_CROSS_ARCH", archStr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Make cannot identify LOCAL_MODULE_HOST_ARCH:= common.
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.Arch().ArchType != Common {
|
2020-09-14 12:43:17 +02:00
|
|
|
a.SetString("LOCAL_MODULE_HOST_ARCH", archStr)
|
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
host = true
|
|
|
|
case Device:
|
|
|
|
// Make cannot identify LOCAL_MODULE_TARGET_ARCH:= common.
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.Arch().ArchType != Common {
|
|
|
|
if base.Target().NativeBridge {
|
|
|
|
hostArchStr := base.Target().NativeBridgeHostArchName
|
2019-03-26 12:39:31 +01:00
|
|
|
if hostArchStr != "" {
|
|
|
|
a.SetString("LOCAL_MODULE_TARGET_ARCH", hostArchStr)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
a.SetString("LOCAL_MODULE_TARGET_ARCH", archStr)
|
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
2022-04-14 01:41:34 +02:00
|
|
|
if !base.InVendorRamdisk() {
|
2021-10-22 01:14:19 +02:00
|
|
|
a.AddPaths("LOCAL_FULL_INIT_RC", base.initRcPaths)
|
2020-12-03 03:55:06 +01:00
|
|
|
}
|
2021-10-22 01:14:19 +02:00
|
|
|
if len(base.vintfFragmentsPaths) > 0 {
|
|
|
|
a.AddPaths("LOCAL_FULL_VINTF_FRAGMENTS", base.vintfFragmentsPaths)
|
2021-04-16 22:41:59 +02:00
|
|
|
}
|
2021-10-22 01:14:19 +02:00
|
|
|
a.SetBoolIfTrue("LOCAL_PROPRIETARY_MODULE", Bool(base.commonProperties.Proprietary))
|
|
|
|
if Bool(base.commonProperties.Vendor) || Bool(base.commonProperties.Soc_specific) {
|
2019-04-04 00:47:29 +02:00
|
|
|
a.SetString("LOCAL_VENDOR_MODULE", "true")
|
|
|
|
}
|
2021-10-22 01:14:19 +02:00
|
|
|
a.SetBoolIfTrue("LOCAL_ODM_MODULE", Bool(base.commonProperties.Device_specific))
|
|
|
|
a.SetBoolIfTrue("LOCAL_PRODUCT_MODULE", Bool(base.commonProperties.Product_specific))
|
|
|
|
a.SetBoolIfTrue("LOCAL_SYSTEM_EXT_MODULE", Bool(base.commonProperties.System_ext_specific))
|
|
|
|
if base.commonProperties.Owner != nil {
|
|
|
|
a.SetString("LOCAL_MODULE_OWNER", *base.commonProperties.Owner)
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if host {
|
2021-10-22 01:14:19 +02:00
|
|
|
makeOs := base.Os().String()
|
|
|
|
if base.Os() == Linux || base.Os() == LinuxBionic || base.Os() == LinuxMusl {
|
2019-04-04 00:47:29 +02:00
|
|
|
makeOs = "linux"
|
|
|
|
}
|
|
|
|
a.SetString("LOCAL_MODULE_HOST_OS", makeOs)
|
|
|
|
a.SetString("LOCAL_IS_HOST_MODULE", "true")
|
|
|
|
}
|
|
|
|
|
|
|
|
prefix := ""
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.ArchSpecific() {
|
|
|
|
switch base.Os().Class {
|
2019-04-04 00:47:29 +02:00
|
|
|
case Host:
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.Target().HostCross {
|
2020-09-14 12:43:17 +02:00
|
|
|
prefix = "HOST_CROSS_"
|
|
|
|
} else {
|
|
|
|
prefix = "HOST_"
|
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
case Device:
|
|
|
|
prefix = "TARGET_"
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-10-22 01:14:19 +02:00
|
|
|
if base.Arch().ArchType != ctx.Config().Targets[base.Os()][0].Arch.ArchType {
|
2019-04-04 00:47:29 +02:00
|
|
|
prefix = "2ND_" + prefix
|
|
|
|
}
|
|
|
|
}
|
2020-07-03 22:18:24 +02:00
|
|
|
|
2023-12-13 22:47:44 +01:00
|
|
|
if licenseMetadata, ok := SingletonModuleProvider(ctx, mod, LicenseMetadataProvider); ok {
|
2021-12-11 00:05:02 +01:00
|
|
|
a.SetPath("LOCAL_SOONG_LICENSE_METADATA", licenseMetadata.LicenseMetadataPath)
|
|
|
|
}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
if _, ok := SingletonModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
|
|
|
|
a.SetBool("LOCAL_SOONG_MODULE_INFO_JSON", true)
|
|
|
|
}
|
|
|
|
|
2020-07-03 22:18:24 +02:00
|
|
|
extraCtx := &androidMkExtraEntriesContext{
|
|
|
|
ctx: ctx,
|
|
|
|
mod: mod,
|
|
|
|
}
|
|
|
|
|
2019-08-28 02:33:16 +02:00
|
|
|
for _, extra := range a.ExtraEntries {
|
2020-07-03 22:18:24 +02:00
|
|
|
extra(extraCtx, a)
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write to footer.
|
|
|
|
fmt.Fprintln(&a.footer, "include "+a.Include)
|
2020-07-03 22:18:24 +02:00
|
|
|
blueprintDir := ctx.ModuleDir(mod)
|
2019-08-29 23:56:03 +02:00
|
|
|
for _, footerFunc := range a.ExtraFooters {
|
2020-12-07 19:23:54 +01:00
|
|
|
footerFunc(&a.footer, name, prefix, blueprintDir)
|
2019-08-29 23:56:03 +02:00
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
func (a *AndroidMkEntries) disabled() bool {
|
|
|
|
return a.Disabled || !a.OutputFile.Valid()
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// write flushes the AndroidMkEntries's in-struct data populated by AndroidMkEntries into the
|
|
|
|
// given Writer object.
|
2019-04-04 00:47:29 +02:00
|
|
|
func (a *AndroidMkEntries) write(w io.Writer) {
|
2023-11-06 22:54:06 +01:00
|
|
|
if a.disabled() {
|
2019-08-29 23:56:03 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-04 00:47:29 +02:00
|
|
|
w.Write(a.header.Bytes())
|
|
|
|
for _, name := range a.entryOrder {
|
2022-12-08 19:41:33 +01:00
|
|
|
AndroidMkEmitAssignList(w, name, a.EntryMap[name])
|
2019-04-04 00:47:29 +02:00
|
|
|
}
|
|
|
|
w.Write(a.footer.Bytes())
|
|
|
|
}
|
|
|
|
|
2019-08-29 23:56:03 +02:00
|
|
|
func (a *AndroidMkEntries) FooterLinesForTests() []string {
|
|
|
|
return strings.Split(string(a.footer.Bytes()), "\n")
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// AndroidMkSingleton is a singleton to collect Android.mk data from all modules and dump them into
|
|
|
|
// the final Android-<product_name>.mk file output.
|
2017-11-29 02:34:01 +01:00
|
|
|
func AndroidMkSingleton() Singleton {
|
2015-07-09 03:13:11 +02:00
|
|
|
return &androidMkSingleton{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type androidMkSingleton struct{}
|
|
|
|
|
2017-11-29 02:34:01 +01:00
|
|
|
func (c *androidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
|
2020-11-16 21:50:29 +01:00
|
|
|
// Skip if Soong wasn't invoked from Make.
|
2020-11-23 06:22:30 +01:00
|
|
|
if !ctx.Config().KatiEnabled() {
|
2015-12-11 22:51:06 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-28 19:19:18 +02:00
|
|
|
var androidMkModulesList []blueprint.Module
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2018-09-28 19:19:18 +02:00
|
|
|
ctx.VisitAllModulesBlueprint(func(module blueprint.Module) {
|
2017-11-29 02:34:01 +01:00
|
|
|
androidMkModulesList = append(androidMkModulesList, module)
|
2016-01-11 21:55:55 +01:00
|
|
|
})
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// Sort the module list by the module names to eliminate random churns, which may erroneously
|
|
|
|
// invoke additional build processes.
|
2019-01-14 21:47:35 +01:00
|
|
|
sort.SliceStable(androidMkModulesList, func(i, j int) bool {
|
|
|
|
return ctx.ModuleName(androidMkModulesList[i]) < ctx.ModuleName(androidMkModulesList[j])
|
|
|
|
})
|
2015-12-18 03:00:23 +01:00
|
|
|
|
2018-03-10 06:22:06 +01:00
|
|
|
transMk := PathForOutput(ctx, "Android"+String(ctx.Config().productVariables.Make_suffix)+".mk")
|
2015-09-24 00:26:20 +02:00
|
|
|
if ctx.Failed() {
|
|
|
|
return
|
|
|
|
}
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
moduleInfoJSON := PathForOutput(ctx, "module-info"+String(ctx.Config().productVariables.Make_suffix)+".json")
|
|
|
|
|
|
|
|
err := translateAndroidMk(ctx, absolutePath(transMk.String()), moduleInfoJSON, androidMkModulesList)
|
2015-07-09 03:13:11 +02:00
|
|
|
if err != nil {
|
|
|
|
ctx.Errorf(err.Error())
|
|
|
|
}
|
|
|
|
|
2017-11-29 02:34:01 +01:00
|
|
|
ctx.Build(pctx, BuildParams{
|
|
|
|
Rule: blueprint.Phony,
|
|
|
|
Output: transMk,
|
2015-07-09 03:13:11 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
func translateAndroidMk(ctx SingletonContext, absMkFile string, moduleInfoJSONPath WritablePath, mods []blueprint.Module) error {
|
2015-07-09 03:13:11 +02:00
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
var moduleInfoJSONs []*ModuleInfoJSON
|
|
|
|
|
2016-02-10 02:43:51 +01:00
|
|
|
fmt.Fprintln(buf, "LOCAL_MODULE_MAKEFILE := $(lastword $(MAKEFILE_LIST))")
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2020-12-21 18:11:10 +01:00
|
|
|
typeStats := make(map[string]int)
|
2015-07-09 03:13:11 +02:00
|
|
|
for _, mod := range mods {
|
2023-11-06 22:54:06 +01:00
|
|
|
err := translateAndroidMkModule(ctx, buf, &moduleInfoJSONs, mod)
|
2015-07-09 03:13:11 +02:00
|
|
|
if err != nil {
|
2021-11-09 21:30:59 +01:00
|
|
|
os.Remove(absMkFile)
|
2015-07-09 03:13:11 +02:00
|
|
|
return err
|
|
|
|
}
|
2016-07-26 01:00:20 +02:00
|
|
|
|
2018-09-28 19:19:18 +02:00
|
|
|
if amod, ok := mod.(Module); ok && ctx.PrimaryModule(amod) == amod {
|
2020-12-21 18:11:10 +01:00
|
|
|
typeStats[ctx.ModuleType(amod)] += 1
|
2016-07-26 01:00:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
keys := []string{}
|
|
|
|
fmt.Fprintln(buf, "\nSTATS.SOONG_MODULE_TYPE :=")
|
2020-12-21 18:11:10 +01:00
|
|
|
for k := range typeStats {
|
2016-07-26 01:00:20 +02:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
for _, mod_type := range keys {
|
|
|
|
fmt.Fprintln(buf, "STATS.SOONG_MODULE_TYPE +=", mod_type)
|
2020-12-21 18:11:10 +01:00
|
|
|
fmt.Fprintf(buf, "STATS.SOONG_MODULE_TYPE.%s := %d\n", mod_type, typeStats[mod_type])
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
err := pathtools.WriteFileIfChanged(absMkFile, buf.Bytes(), 0666)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return writeModuleInfoJSON(ctx, moduleInfoJSONs, moduleInfoJSONPath)
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
func writeModuleInfoJSON(ctx SingletonContext, moduleInfoJSONs []*ModuleInfoJSON, moduleInfoJSONPath WritablePath) error {
|
|
|
|
moduleInfoJSONBuf := &strings.Builder{}
|
|
|
|
moduleInfoJSONBuf.WriteString("[")
|
|
|
|
for i, moduleInfoJSON := range moduleInfoJSONs {
|
|
|
|
if i != 0 {
|
|
|
|
moduleInfoJSONBuf.WriteString(",\n")
|
|
|
|
}
|
|
|
|
moduleInfoJSONBuf.WriteString("{")
|
|
|
|
moduleInfoJSONBuf.WriteString(strconv.Quote(moduleInfoJSON.core.RegisterName))
|
|
|
|
moduleInfoJSONBuf.WriteString(":")
|
|
|
|
err := encodeModuleInfoJSON(moduleInfoJSONBuf, moduleInfoJSON)
|
|
|
|
moduleInfoJSONBuf.WriteString("}")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
moduleInfoJSONBuf.WriteString("]")
|
|
|
|
WriteFileRule(ctx, moduleInfoJSONPath, moduleInfoJSONBuf.String())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func translateAndroidMkModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON, mod blueprint.Module) error {
|
2018-09-06 01:23:54 +02:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
panic(fmt.Errorf("%s in translateAndroidMkModule for module %s variant %s",
|
|
|
|
r, ctx.ModuleName(mod), ctx.ModuleSubDir(mod)))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-01-07 04:34:31 +01:00
|
|
|
// Additional cases here require review for correct license propagation to make.
|
2023-11-06 22:54:06 +01:00
|
|
|
var err error
|
2018-09-28 19:19:18 +02:00
|
|
|
switch x := mod.(type) {
|
|
|
|
case AndroidMkDataProvider:
|
2023-11-06 22:54:06 +01:00
|
|
|
err = translateAndroidModule(ctx, w, moduleInfoJSONs, mod, x)
|
2018-09-28 19:19:18 +02:00
|
|
|
case bootstrap.GoBinaryTool:
|
2023-11-06 22:54:06 +01:00
|
|
|
err = translateGoBinaryModule(ctx, w, mod, x)
|
2019-04-04 00:47:29 +02:00
|
|
|
case AndroidMkEntriesProvider:
|
2023-11-06 22:54:06 +01:00
|
|
|
err = translateAndroidMkEntriesModule(ctx, w, moduleInfoJSONs, mod, x)
|
2018-09-28 19:19:18 +02:00
|
|
|
default:
|
2021-01-07 04:34:31 +01:00
|
|
|
// Not exported to make so no make variables to set.
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
2023-11-06 22:54:06 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
2018-09-28 19:19:18 +02:00
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// A simple, special Android.mk entry output func to make it possible to build blueprint tools using
|
|
|
|
// m by making them phony targets.
|
2018-09-28 19:19:18 +02:00
|
|
|
func translateGoBinaryModule(ctx SingletonContext, w io.Writer, mod blueprint.Module,
|
|
|
|
goBinary bootstrap.GoBinaryTool) error {
|
|
|
|
|
|
|
|
name := ctx.ModuleName(mod)
|
|
|
|
fmt.Fprintln(w, ".PHONY:", name)
|
|
|
|
fmt.Fprintln(w, name+":", goBinary.InstallPath())
|
|
|
|
fmt.Fprintln(w, "")
|
2021-01-07 04:34:31 +01:00
|
|
|
// Assuming no rules in make include go binaries in distributables.
|
|
|
|
// If the assumption is wrong, make will fail to build without the necessary .meta_lic and .meta_module files.
|
|
|
|
// In that case, add the targets and rules here to build a .meta_lic file for `name` and a .meta_module for
|
|
|
|
// `goBinary.InstallPath()` pointing to the `name`.meta_lic file.
|
2018-09-28 19:19:18 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-07-03 22:18:24 +02:00
|
|
|
func (data *AndroidMkData) fillInData(ctx fillInEntriesContext, mod blueprint.Module) {
|
2019-04-04 00:47:29 +02:00
|
|
|
// Get the preamble content through AndroidMkEntries logic.
|
2020-06-24 16:26:26 +02:00
|
|
|
data.Entries = AndroidMkEntries{
|
2019-04-04 00:47:29 +02:00
|
|
|
Class: data.Class,
|
|
|
|
SubName: data.SubName,
|
2020-06-15 07:24:19 +02:00
|
|
|
DistFiles: data.DistFiles,
|
2019-04-04 00:47:29 +02:00
|
|
|
OutputFile: data.OutputFile,
|
|
|
|
Disabled: data.Disabled,
|
|
|
|
Include: data.Include,
|
|
|
|
Required: data.Required,
|
|
|
|
Host_required: data.Host_required,
|
|
|
|
Target_required: data.Target_required,
|
2016-06-14 02:19:03 +02:00
|
|
|
}
|
2020-07-03 22:18:24 +02:00
|
|
|
data.Entries.fillInEntries(ctx, mod)
|
2016-06-14 02:19:03 +02:00
|
|
|
|
2019-07-11 09:18:47 +02:00
|
|
|
// copy entries back to data since it is used in Custom
|
2020-06-24 16:26:26 +02:00
|
|
|
data.Required = data.Entries.Required
|
|
|
|
data.Host_required = data.Entries.Host_required
|
|
|
|
data.Target_required = data.Entries.Target_required
|
2019-07-11 09:18:47 +02:00
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// A support func for the deprecated AndroidMkDataProvider interface. Use AndroidMkEntryProvider
|
|
|
|
// instead.
|
2023-11-06 22:54:06 +01:00
|
|
|
func translateAndroidModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON,
|
|
|
|
mod blueprint.Module, provider AndroidMkDataProvider) error {
|
2019-07-11 09:18:47 +02:00
|
|
|
|
|
|
|
amod := mod.(Module).base()
|
|
|
|
if shouldSkipAndroidMkProcessing(amod) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
data := provider.AndroidMk()
|
|
|
|
if data.Include == "" {
|
|
|
|
data.Include = "$(BUILD_PREBUILT)"
|
|
|
|
}
|
|
|
|
|
2020-07-03 22:18:24 +02:00
|
|
|
data.fillInData(ctx, mod)
|
2019-07-11 09:18:47 +02:00
|
|
|
|
2017-08-11 02:07:28 +02:00
|
|
|
prefix := ""
|
|
|
|
if amod.ArchSpecific() {
|
|
|
|
switch amod.Os().Class {
|
|
|
|
case Host:
|
2020-09-14 12:43:17 +02:00
|
|
|
if amod.Target().HostCross {
|
|
|
|
prefix = "HOST_CROSS_"
|
|
|
|
} else {
|
|
|
|
prefix = "HOST_"
|
|
|
|
}
|
2017-08-11 02:07:28 +02:00
|
|
|
case Device:
|
|
|
|
prefix = "TARGET_"
|
2016-03-24 21:14:12 +01:00
|
|
|
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
2018-10-11 02:02:29 +02:00
|
|
|
if amod.Arch().ArchType != ctx.Config().Targets[amod.Os()][0].Arch.ArchType {
|
2017-08-11 02:07:28 +02:00
|
|
|
prefix = "2ND_" + prefix
|
|
|
|
}
|
2016-02-10 02:43:51 +01:00
|
|
|
}
|
2015-07-09 03:13:11 +02:00
|
|
|
|
2019-04-04 00:47:29 +02:00
|
|
|
name := provider.BaseModuleName()
|
2017-08-11 02:07:28 +02:00
|
|
|
blueprintDir := filepath.Dir(ctx.BlueprintFile(mod))
|
|
|
|
|
|
|
|
if data.Custom != nil {
|
2021-01-07 04:34:31 +01:00
|
|
|
// List of module types allowed to use .Custom(...)
|
|
|
|
// Additions to the list require careful review for proper license handling.
|
2020-07-03 22:18:24 +02:00
|
|
|
switch reflect.TypeOf(mod).String() { // ctx.ModuleType(mod) doesn't work: aidl_interface creates phony without type
|
2021-01-07 04:34:31 +01:00
|
|
|
case "*aidl.aidlApi": // writes non-custom before adding .phony
|
|
|
|
case "*aidl.aidlMapping": // writes non-custom before adding .phony
|
|
|
|
case "*android.customModule": // appears in tests only
|
2021-07-14 06:52:04 +02:00
|
|
|
case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
|
2021-01-07 04:34:31 +01:00
|
|
|
case "*apex.apexBundle": // license properties written
|
|
|
|
case "*bpf.bpf": // license properties written (both for module and objs)
|
|
|
|
case "*genrule.Module": // writes non-custom before adding .phony
|
|
|
|
case "*java.SystemModules": // doesn't go through base_rules
|
|
|
|
case "*java.systemModulesImport": // doesn't go through base_rules
|
|
|
|
case "*phony.phony": // license properties written
|
2023-12-20 03:37:52 +01:00
|
|
|
case "*phony.PhonyRule": // writes phony deps and acts like `.PHONY`
|
2021-01-07 04:34:31 +01:00
|
|
|
case "*selinux.selinuxContextsModule": // license properties written
|
|
|
|
case "*sysprop.syspropLibrary": // license properties written
|
|
|
|
default:
|
2021-09-03 00:33:10 +02:00
|
|
|
if !ctx.Config().IsEnvFalse("ANDROID_REQUIRE_LICENSES") {
|
2021-01-07 04:34:31 +01:00
|
|
|
return fmt.Errorf("custom make rules not allowed for %q (%q) module %q", ctx.ModuleType(mod), reflect.TypeOf(mod), ctx.ModuleName(mod))
|
|
|
|
}
|
|
|
|
}
|
2017-08-11 02:07:28 +02:00
|
|
|
data.Custom(w, name, prefix, blueprintDir, data)
|
|
|
|
} else {
|
|
|
|
WriteAndroidMkData(w, data)
|
|
|
|
}
|
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
if !data.Entries.disabled() {
|
|
|
|
if moduleInfoJSON, ok := SingletonModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
|
|
|
|
*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-11 02:07:28 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// A support func for the deprecated AndroidMkDataProvider interface. Use AndroidMkEntryProvider
|
|
|
|
// instead.
|
2017-08-11 02:07:28 +02:00
|
|
|
func WriteAndroidMkData(w io.Writer, data AndroidMkData) {
|
2023-11-06 22:54:06 +01:00
|
|
|
if data.Entries.disabled() {
|
2017-08-11 02:07:28 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-06-24 16:26:26 +02:00
|
|
|
// write preamble via Entries
|
|
|
|
data.Entries.footer = bytes.Buffer{}
|
|
|
|
data.Entries.write(w)
|
2017-08-11 02:07:28 +02:00
|
|
|
|
2016-01-04 23:34:37 +01:00
|
|
|
for _, extra := range data.Extra {
|
2017-08-11 01:32:23 +02:00
|
|
|
extra(w, data.OutputFile.Path())
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
|
|
|
|
2017-09-07 22:20:25 +02:00
|
|
|
fmt.Fprintln(w, "include "+data.Include)
|
2015-07-09 03:13:11 +02:00
|
|
|
}
|
2019-04-02 03:37:36 +02:00
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
func translateAndroidMkEntriesModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON,
|
|
|
|
mod blueprint.Module, provider AndroidMkEntriesProvider) error {
|
2019-04-04 00:47:29 +02:00
|
|
|
if shouldSkipAndroidMkProcessing(mod.(Module).base()) {
|
|
|
|
return nil
|
2019-04-02 03:37:36 +02:00
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
entriesList := provider.AndroidMkEntries()
|
|
|
|
|
2021-01-07 04:34:31 +01:00
|
|
|
// Any new or special cases here need review to verify correct propagation of license information.
|
2023-11-06 22:54:06 +01:00
|
|
|
for _, entries := range entriesList {
|
2020-07-03 22:18:24 +02:00
|
|
|
entries.fillInEntries(ctx, mod)
|
2019-12-03 05:24:29 +01:00
|
|
|
entries.write(w)
|
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
|
2023-11-06 22:54:06 +01:00
|
|
|
if len(entriesList) > 0 && !entriesList[0].disabled() {
|
|
|
|
if moduleInfoJSON, ok := SingletonModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
|
|
|
|
*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-04 00:47:29 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-12 01:46:56 +02:00
|
|
|
func ShouldSkipAndroidMkProcessing(module Module) bool {
|
|
|
|
return shouldSkipAndroidMkProcessing(module.base())
|
|
|
|
}
|
|
|
|
|
2019-04-04 00:47:29 +02:00
|
|
|
func shouldSkipAndroidMkProcessing(module *ModuleBase) bool {
|
|
|
|
if !module.commonProperties.NamespaceExportedToMake {
|
|
|
|
// TODO(jeffrygaston) do we want to validate that there are no modules being
|
|
|
|
// exported to Kati that depend on this module?
|
|
|
|
return true
|
2019-04-02 03:37:36 +02:00
|
|
|
}
|
2019-04-04 00:47:29 +02:00
|
|
|
|
2021-10-17 09:22:33 +02:00
|
|
|
// On Mac, only expose host darwin modules to Make, as that's all we claim to support.
|
|
|
|
// In reality, some of them depend on device-built (Java) modules, so we can't disable all
|
|
|
|
// device modules in Soong, but we can hide them from Make (and thus the build user interface)
|
|
|
|
if runtime.GOOS == "darwin" && module.Os() != Darwin {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-10-19 09:22:06 +02:00
|
|
|
// Only expose the primary Darwin target, as Make does not understand Darwin+Arm64
|
|
|
|
if module.Os() == Darwin && module.Target().HostCross {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-04-04 00:47:29 +02:00
|
|
|
return !module.Enabled() ||
|
2020-12-16 19:20:23 +01:00
|
|
|
module.commonProperties.HideFromMake ||
|
2019-04-04 00:47:29 +02:00
|
|
|
// Make does not understand LinuxBionic
|
2022-06-25 03:45:58 +02:00
|
|
|
module.Os() == LinuxBionic ||
|
|
|
|
// Make does not understand LinuxMusl, except when we are building with USE_HOST_MUSL=true
|
|
|
|
// and all host binaries are LinuxMusl
|
|
|
|
(module.Os() == LinuxMusl && module.Target().HostCross)
|
2019-04-02 03:37:36 +02:00
|
|
|
}
|
2020-09-21 21:11:02 +02:00
|
|
|
|
2020-11-16 21:50:29 +01:00
|
|
|
// A utility func to format LOCAL_TEST_DATA outputs. See the comments on DataPath to understand how
|
|
|
|
// to use this func.
|
2023-11-15 21:39:40 +01:00
|
|
|
func androidMkDataPaths(data []DataPath) []string {
|
2020-09-21 21:11:02 +02:00
|
|
|
var testFiles []string
|
|
|
|
for _, d := range data {
|
|
|
|
rel := d.SrcPath.Rel()
|
2023-11-15 21:39:40 +01:00
|
|
|
if d.WithoutRel {
|
|
|
|
rel = d.SrcPath.Base()
|
|
|
|
}
|
2020-09-21 21:11:02 +02:00
|
|
|
path := d.SrcPath.String()
|
2020-11-16 21:50:29 +01:00
|
|
|
// LOCAL_TEST_DATA requires the rel portion of the path to be removed from the path.
|
2020-09-21 21:11:02 +02:00
|
|
|
if !strings.HasSuffix(path, rel) {
|
|
|
|
panic(fmt.Errorf("path %q does not end with %q", path, rel))
|
|
|
|
}
|
|
|
|
path = strings.TrimSuffix(path, rel)
|
|
|
|
testFileString := path + ":" + rel
|
|
|
|
if len(d.RelativeInstallPath) > 0 {
|
|
|
|
testFileString += ":" + d.RelativeInstallPath
|
|
|
|
}
|
|
|
|
testFiles = append(testFiles, testFileString)
|
|
|
|
}
|
|
|
|
return testFiles
|
|
|
|
}
|
2022-12-08 19:41:33 +01:00
|
|
|
|
|
|
|
// AndroidMkEmitAssignList emits the line
|
|
|
|
//
|
|
|
|
// VAR := ITEM ...
|
|
|
|
//
|
|
|
|
// Items are the elements to the given set of lists
|
|
|
|
// If all the passed lists are empty, no line will be emitted
|
|
|
|
func AndroidMkEmitAssignList(w io.Writer, varName string, lists ...[]string) {
|
|
|
|
doPrint := false
|
|
|
|
for _, l := range lists {
|
|
|
|
if doPrint = len(l) > 0; doPrint {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !doPrint {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fmt.Fprint(w, varName, " :=")
|
|
|
|
for _, l := range lists {
|
|
|
|
for _, item := range l {
|
|
|
|
fmt.Fprint(w, " ", item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintln(w)
|
|
|
|
}
|