starlark_dialect_build_targets/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5pub mod testutil;
6
7use {
8    anyhow::{anyhow, Result},
9    linked_hash_map::LinkedHashMap,
10    log::warn,
11    path_dedot::ParseDot,
12    starlark::{
13        environment::{Environment, EnvironmentError, TypeValues},
14        eval::call_stack::CallStack,
15        values::{
16            error::{RuntimeError, ValueError, INCORRECT_PARAMETER_TYPE_ERROR_CODE},
17            none::NoneType,
18            {Mutable, TypedValue, Value, ValueResult},
19        },
20        {
21            starlark_fun, starlark_module, starlark_parse_param_type, starlark_signature,
22            starlark_signature_extraction, starlark_signatures,
23        },
24    },
25    std::{
26        borrow::Cow,
27        collections::{BTreeMap, HashMap},
28        os::raw::c_ulong,
29        path::{Path, PathBuf},
30    },
31};
32
33/// How a resolved target can be run.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum RunMode {
36    /// Target cannot be run.
37    None,
38    /// Target is run by executing a path.
39    Path { path: PathBuf },
40}
41
42/// Represents a resolved target.
43#[derive(Debug, Clone)]
44pub struct ResolvedTarget {
45    /// How the built target can be run.
46    pub run_mode: RunMode,
47
48    /// Where build artifacts are stored on the filesystem.
49    pub output_path: PathBuf,
50}
51
52impl ResolvedTarget {
53    pub fn run(&self) -> Result<()> {
54        match &self.run_mode {
55            RunMode::None => Ok(()),
56            RunMode::Path { path } => {
57                let status = std::process::Command::new(path)
58                    .current_dir(path.parent().unwrap())
59                    .status()?;
60
61                if status.success() {
62                    Ok(())
63                } else {
64                    Err(anyhow!("cargo run failed"))
65                }
66            }
67        }
68    }
69}
70
71pub struct ResolvedTargetValue {
72    pub inner: ResolvedTarget,
73}
74
75impl TypedValue for ResolvedTargetValue {
76    type Holder = Mutable<ResolvedTargetValue>;
77    const TYPE: &'static str = "ResolvedTarget";
78
79    fn values_for_descendant_check_and_freeze(&self) -> Box<dyn Iterator<Item = Value>> {
80        Box::new(std::iter::empty())
81    }
82}
83
84impl From<ResolvedTarget> for ResolvedTargetValue {
85    fn from(t: ResolvedTarget) -> Self {
86        Self { inner: t }
87    }
88}
89
90/// Represents a registered target in the Starlark environment.
91#[derive(Debug, Clone)]
92pub struct Target {
93    /// The Starlark callable registered to this target.
94    pub callable: Value,
95
96    /// Other targets this one depends on.
97    pub depends: Vec<String>,
98
99    /// What calling callable returned, if it has been called.
100    pub resolved_value: Option<Value>,
101
102    /// The `ResolvedTarget` instance this target's build() returned.
103    ///
104    /// TODO consider making this an Arc<T> so we don't have to clone it.
105    pub built_target: Option<ResolvedTarget>,
106}
107
108/// Holds execution context for a Starlark environment.
109#[derive(Debug)]
110pub struct EnvironmentContext {
111    /// Current working directory.
112    cwd: PathBuf,
113
114    /// Default output directory.
115    build_path: PathBuf,
116
117    /// Optional path prefix to insert between the build path and the target name.
118    target_build_path_prefix: Option<PathBuf>,
119
120    /// Registered targets.
121    ///
122    /// A target is a name and a Starlark callable.
123    targets: BTreeMap<String, Target>,
124
125    /// Order targets are registered in.
126    targets_order: Vec<String>,
127
128    /// Name of the default target.
129    default_target: Option<String>,
130
131    /// List of targets to resolve.
132    resolve_targets: Option<Vec<String>>,
133
134    // TODO figure out a generic way to express build script mode.
135    /// Name of default target to resolve in build script mode.
136    pub default_build_script_target: Option<String>,
137
138    /// Whether we are operating in Rust build script mode.
139    ///
140    /// This will change the default target to resolve.
141    pub build_script_mode: bool,
142}
143
144impl EnvironmentContext {
145    pub fn new(cwd: PathBuf) -> Self {
146        let build_path = cwd.join("build");
147
148        Self {
149            cwd,
150            build_path,
151            target_build_path_prefix: None,
152            targets: BTreeMap::new(),
153            targets_order: vec![],
154            default_target: None,
155            resolve_targets: None,
156            default_build_script_target: None,
157            build_script_mode: false,
158        }
159    }
160
161    /// Obtain the current working directory for this context.
162    pub fn cwd(&self) -> &Path {
163        &self.cwd
164    }
165
166    /// Directory to use for the build path.
167    pub fn build_path(&self) -> &Path {
168        &self.build_path
169    }
170
171    /// Update the directory to use for the build path.
172    pub fn set_build_path(&mut self, path: &Path) -> Result<()> {
173        let path = if path.is_relative() {
174            self.cwd.join(path)
175        } else {
176            path.to_path_buf()
177        }
178        .parse_dot()?
179        .to_path_buf();
180
181        self.build_path = path;
182
183        Ok(())
184    }
185
186    /// Resolve an absolute filesystem path from a path input.
187    ///
188    /// If the incoming path is absolute, it is returned. Otherwise relative
189    /// paths are joined with the current build path.
190    pub fn resolve_path(&self, path: impl AsRef<Path>) -> PathBuf {
191        let path = path.as_ref();
192
193        if path.is_absolute() || path.to_string_lossy().starts_with('/') {
194            path.to_path_buf()
195        } else {
196            self.build_path.join(path)
197        }
198    }
199
200    /// Set the path prefix to use for per-target build paths.
201    ///
202    /// If defined, target build paths are of the form `<build_path>/<prefix>/<target>`.
203    /// Otherwise they are `<build_path>/<target>`.
204    pub fn set_target_build_path_prefix<P: AsRef<Path>>(&mut self, prefix: Option<P>) {
205        self.target_build_path_prefix = prefix.map(|p| p.as_ref().to_path_buf());
206    }
207
208    /// Obtain the directory to use to build a named target.
209    pub fn target_build_path(&self, target: &str) -> PathBuf {
210        if let Some(prefix) = &self.target_build_path_prefix {
211            self.build_path.join(prefix).join(target)
212        } else {
213            self.build_path.join(target)
214        }
215    }
216
217    /// Obtain all registered targets.
218    pub fn targets(&self) -> &BTreeMap<String, Target> {
219        &self.targets
220    }
221
222    /// Obtain the default target to resolve.
223    pub fn default_target(&self) -> Option<&str> {
224        self.default_target.as_deref()
225    }
226
227    /// Obtain a named target.
228    pub fn get_target(&self, target: &str) -> Option<&Target> {
229        self.targets.get(target)
230    }
231
232    /// Obtain a mutable named target.
233    pub fn get_target_mut(&mut self, target: &str) -> Option<&mut Target> {
234        self.targets.get_mut(target)
235    }
236
237    /// Set the list of targets to resolve.
238    pub fn set_resolve_targets(&mut self, targets: Vec<String>) {
239        self.resolve_targets = Some(targets);
240    }
241
242    /// Obtain the order that targets were registered in.
243    pub fn targets_order(&self) -> &Vec<String> {
244        &self.targets_order
245    }
246
247    /// Register a named target.
248    pub fn register_target(
249        &mut self,
250        target: String,
251        callable: Value,
252        depends: Vec<String>,
253        default: bool,
254        default_build_script: bool,
255    ) {
256        if !self.targets.contains_key(&target) {
257            self.targets_order.push(target.clone());
258        }
259
260        self.targets.insert(
261            target.clone(),
262            Target {
263                callable,
264                depends,
265                resolved_value: None,
266                built_target: None,
267            },
268        );
269
270        if default || self.default_target.is_none() {
271            self.default_target = Some(target.clone());
272        }
273
274        if default_build_script || self.default_build_script_target.is_none() {
275            self.default_build_script_target = Some(target);
276        }
277    }
278
279    /// Determine what targets should be resolved.
280    ///
281    /// This isn't the full list of targets that will be resolved, only the main
282    /// targets that we will instruct the resolver to resolve.
283    pub fn targets_to_resolve(&self) -> Vec<String> {
284        if let Some(targets) = &self.resolve_targets {
285            targets.clone()
286        } else if self.build_script_mode && self.default_build_script_target.is_some() {
287            vec![self.default_build_script_target.clone().unwrap()]
288        } else if let Some(target) = &self.default_target {
289            vec![target.to_string()]
290        } else {
291            Vec::new()
292        }
293    }
294}
295
296impl TypedValue for EnvironmentContext {
297    type Holder = Mutable<EnvironmentContext>;
298    const TYPE: &'static str = "EnvironmentContext";
299
300    fn values_for_descendant_check_and_freeze(&self) -> Box<dyn Iterator<Item = Value>> {
301        Box::new(std::iter::empty())
302    }
303}
304
305#[derive(Default)]
306struct PlaceholderContext {}
307
308impl TypedValue for PlaceholderContext {
309    type Holder = Mutable<PlaceholderContext>;
310    const TYPE: &'static str = "BuildTargets";
311
312    fn values_for_descendant_check_and_freeze(&self) -> Box<dyn Iterator<Item = Value>> {
313        Box::new(std::iter::empty())
314    }
315}
316
317pub fn required_type_arg(arg_name: &str, arg_type: &str, value: &Value) -> Result<(), ValueError> {
318    let t = value.get_type();
319    if t == arg_type {
320        Ok(())
321    } else {
322        Err(ValueError::from(RuntimeError {
323            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
324            message: format!(
325                "function expects a {} for {}; got type {}",
326                arg_type, arg_name, t
327            ),
328            label: format!("expect type {}; got {}", arg_type, t),
329        }))
330    }
331}
332
333pub fn optional_type_arg(arg_name: &str, arg_type: &str, value: &Value) -> Result<(), ValueError> {
334    match value.get_type() {
335        "NoneType" => Ok(()),
336        _ => required_type_arg(arg_name, arg_type, value),
337    }
338}
339
340pub fn optional_str_arg(name: &str, value: &Value) -> Result<Option<String>, ValueError> {
341    match value.get_type() {
342        "NoneType" => Ok(None),
343        "string" => Ok(Some(value.to_str())),
344        t => Err(ValueError::from(RuntimeError {
345            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
346            message: format!(
347                "function expects an optional string for {}; got type {}",
348                name, t
349            ),
350            label: format!("expected type string; got {}", t),
351        })),
352    }
353}
354
355pub fn optional_bool_arg(name: &str, value: &Value) -> Result<Option<bool>, ValueError> {
356    match value.get_type() {
357        "NoneType" => Ok(None),
358        "bool" => Ok(Some(value.to_bool())),
359        t => Err(ValueError::from(RuntimeError {
360            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
361            message: format!(
362                "function expects an optional bool for {}; got type {}",
363                name, t
364            ),
365            label: format!("expected type bool; got {}", t),
366        })),
367    }
368}
369
370pub fn optional_int_arg(name: &str, value: &Value) -> Result<Option<i64>, ValueError> {
371    match value.get_type() {
372        "NoneType" => Ok(None),
373        "int" => Ok(Some(value.to_int()?)),
374        t => Err(ValueError::from(RuntimeError {
375            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
376            message: format!(
377                "function expected an optional int for {}; got type {}",
378                name, t
379            ),
380            label: format!("expected type int; got {}", t),
381        })),
382    }
383}
384
385pub fn required_list_arg(
386    arg_name: &str,
387    value_type: &str,
388    value: &Value,
389) -> Result<(), ValueError> {
390    match value.get_type() {
391        "list" => {
392            for v in &value.iter()? {
393                if v.get_type() == value_type {
394                    Ok(())
395                } else {
396                    Err(ValueError::from(RuntimeError {
397                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
398                        message: format!(
399                            "list {} expects values of type {}; got {}",
400                            arg_name,
401                            value_type,
402                            v.get_type()
403                        ),
404                        label: format!("expected type {}; got {}", value_type, v.get_type()),
405                    }))
406                }?;
407            }
408            Ok(())
409        }
410        t => Err(ValueError::from(RuntimeError {
411            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
412            message: format!("function expects a list for {}; got type {}", arg_name, t),
413            label: format!("expected type list; got {}", t),
414        })),
415    }
416}
417
418pub fn optional_list_arg(
419    arg_name: &str,
420    value_type: &str,
421    value: &Value,
422) -> Result<(), ValueError> {
423    if value.get_type() == "NoneType" {
424        return Ok(());
425    }
426
427    required_list_arg(arg_name, value_type, value)
428}
429
430pub fn required_dict_arg(
431    arg_name: &str,
432    key_type: &str,
433    value_type: &str,
434    value: &Value,
435) -> Result<(), ValueError> {
436    match value.get_type() {
437        "dict" => {
438            for k in &value.iter()? {
439                if k.get_type() == key_type {
440                    Ok(())
441                } else {
442                    Err(ValueError::from(RuntimeError {
443                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
444                        message: format!(
445                            "dict {} expects keys of type {}; got {}",
446                            arg_name,
447                            key_type,
448                            k.get_type()
449                        ),
450                        label: format!("expected type {}; got {}", key_type, k.get_type()),
451                    }))
452                }?;
453
454                let v = value.at(k.clone())?;
455
456                if v.get_type() == value_type {
457                    Ok(())
458                } else {
459                    Err(ValueError::from(RuntimeError {
460                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
461                        message: format!(
462                            "dict {} expects values of type {}; got {}",
463                            arg_name,
464                            value_type,
465                            v.get_type(),
466                        ),
467                        label: format!("expected type {}; got {}", value_type, v.get_type()),
468                    }))
469                }?;
470            }
471            Ok(())
472        }
473        t => Err(ValueError::from(RuntimeError {
474            code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
475            message: format!("function expects a dict for {}; got type {}", arg_name, t),
476            label: format!("expected type dict; got {}", t),
477        })),
478    }
479}
480
481pub fn optional_dict_arg(
482    arg_name: &str,
483    key_type: &str,
484    value_type: &str,
485    value: &Value,
486) -> Result<(), ValueError> {
487    if value.get_type() == "NoneType" {
488        return Ok(());
489    }
490
491    required_dict_arg(arg_name, key_type, value_type, value)
492}
493
494pub trait ToOptional<T> {
495    fn to_optional(&self) -> Option<T>;
496}
497
498impl ToOptional<bool> for Value {
499    fn to_optional(&self) -> Option<bool> {
500        if self.get_type() == "NoneType" {
501            None
502        } else {
503            Some(self.to_bool())
504        }
505    }
506}
507
508impl ToOptional<String> for Value {
509    fn to_optional(&self) -> Option<String> {
510        if self.get_type() == "NoneType" {
511            None
512        } else {
513            Some(self.to_string())
514        }
515    }
516}
517
518impl ToOptional<Cow<'static, str>> for Value {
519    fn to_optional(&self) -> Option<Cow<'static, str>> {
520        if self.get_type() == "NoneType" {
521            None
522        } else {
523            Some(Cow::Owned(self.to_string()))
524        }
525    }
526}
527
528impl ToOptional<PathBuf> for Value {
529    fn to_optional(&self) -> Option<PathBuf> {
530        if self.get_type() == "NoneType" {
531            None
532        } else {
533            Some(PathBuf::from(self.to_string()))
534        }
535    }
536}
537
538pub trait TryToOptional<T> {
539    fn try_to_optional(&self) -> Result<Option<T>, ValueError>;
540}
541
542impl TryToOptional<c_ulong> for Value {
543    fn try_to_optional(&self) -> Result<Option<c_ulong>, ValueError> {
544        if self.get_type() == "NoneType" {
545            Ok(None)
546        } else {
547            Ok(Some(self.to_int()? as c_ulong))
548        }
549    }
550}
551
552impl TryToOptional<i64> for Value {
553    fn try_to_optional(&self) -> Result<Option<i64>, ValueError> {
554        match self.get_type() {
555            "NoneType" => Ok(None),
556            "int" => Ok(Some(self.to_int()?)),
557            t => Err(ValueError::from(RuntimeError {
558                code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
559                message: format!("expected int or NoneType; got {}", t),
560                label: "".to_string(),
561            })),
562        }
563    }
564}
565
566impl TryToOptional<Vec<String>> for Value {
567    fn try_to_optional(&self) -> Result<Option<Vec<String>>, ValueError> {
568        if self.get_type() == "NoneType" {
569            Ok(None)
570        } else {
571            let values = self.to_vec()?;
572
573            Ok(Some(
574                values.iter().map(|x| x.to_string()).collect::<Vec<_>>(),
575            ))
576        }
577    }
578}
579
580impl TryToOptional<Vec<Cow<'static, str>>> for Value {
581    fn try_to_optional(&self) -> Result<Option<Vec<Cow<'static, str>>>, ValueError> {
582        if self.get_type() == "NoneType" {
583            Ok(None)
584        } else {
585            let values = self.to_vec()?;
586
587            Ok(Some(
588                values
589                    .iter()
590                    .map(|x| Cow::Owned(x.to_string()))
591                    .collect::<Vec<_>>(),
592            ))
593        }
594    }
595}
596
597impl TryToOptional<Vec<PathBuf>> for Value {
598    fn try_to_optional(&self) -> Result<Option<Vec<PathBuf>>, ValueError> {
599        if self.get_type() == "NoneType" {
600            Ok(None)
601        } else {
602            let values = self.to_vec()?;
603
604            Ok(Some(
605                values
606                    .iter()
607                    .map(|x| PathBuf::from(x.to_string()))
608                    .collect::<Vec<_>>(),
609            ))
610        }
611    }
612}
613
614type StringHashMap = HashMap<Cow<'static, str>, Cow<'static, str>>;
615
616impl TryToOptional<StringHashMap> for Value {
617    fn try_to_optional(&self) -> Result<Option<StringHashMap>, ValueError> {
618        match self.get_type() {
619            "NoneType" => Ok(None),
620            "dict" => {
621                let mut res = HashMap::new();
622
623                for key in &self.iter()? {
624                    let value = self.at(key.clone())?;
625
626                    res.insert(Cow::Owned(key.to_string()), Cow::Owned(value.to_string()));
627                }
628
629                Ok(Some(res))
630            }
631            t => Err(ValueError::from(RuntimeError {
632                code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
633                message: format!("expected dict or NoneType; got {}", t),
634                label: "".to_string(),
635            })),
636        }
637    }
638}
639
640impl TryToOptional<HashMap<Cow<'static, str>, StringHashMap>> for Value {
641    fn try_to_optional(
642        &self,
643    ) -> Result<Option<HashMap<Cow<'static, str>, StringHashMap>>, ValueError> {
644        match self.get_type() {
645            "NoneType" => Ok(None),
646            "dict" => {
647                let mut res = HashMap::new();
648
649                for key in &self.iter()? {
650                    let value = self.at(key.clone())?;
651
652                    let value: Option<HashMap<Cow<'static, str>, Cow<'static, str>>> =
653                        value.try_to_optional()?;
654
655                    match value {
656                        Some(v) => {
657                            res.insert(Cow::Owned(key.to_string()), v);
658                        }
659                        None => {
660                            return Err(ValueError::from(RuntimeError {
661                                code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
662                                message: "expected dict[string, string], got None".to_string(),
663                                label: "".to_string(),
664                            }));
665                        }
666                    }
667                }
668
669                Ok(Some(res))
670            }
671            t => Err(ValueError::from(RuntimeError {
672                code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
673                message: format!("expected dict or NoneType; got {}", t),
674                label: "".to_string(),
675            })),
676        }
677    }
678}
679
680impl TryToOptional<Vec<StringHashMap>> for Value {
681    fn try_to_optional(&self) -> Result<Option<Vec<StringHashMap>>, ValueError> {
682        match self.get_type() {
683            "NoneType" => Ok(None),
684            "list" => {
685                let mut res = Vec::new();
686
687                for item in &self.iter()? {
688                    match item.get_type() {
689                        "dict" => {
690                            let value: Option<StringHashMap> = item.try_to_optional()?;
691
692                            match value {
693                                Some(value) => {
694                                    res.push(value);
695                                }
696                                None => {
697                                    return Err(ValueError::from(RuntimeError {
698                                        code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
699                                        message: "expected dict[string, string], got None"
700                                            .to_string(),
701                                        label: "".to_string(),
702                                    }));
703                                }
704                            }
705                        }
706                        t => {
707                            return Err(ValueError::from(RuntimeError {
708                                code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
709                                message: format!("expected dict[string, string], got {}", t),
710                                label: "".to_string(),
711                            }));
712                        }
713                    }
714                }
715
716                Ok(Some(res))
717            }
718            t => Err(ValueError::from(RuntimeError {
719                code: INCORRECT_PARAMETER_TYPE_ERROR_CODE,
720                message: format!("expected list or NoneType; got {}", t),
721                label: "".to_string(),
722            })),
723        }
724    }
725}
726
727const ENVIRONMENT_CONTEXT_SYMBOL: &str = "BUILD_CONTEXT";
728
729/// Obtain the `Value` holding the `EnvironmentContext` for a Starlark environment.
730///
731/// This is a helper function. The returned `Value` needs to be casted
732/// to have much value.
733pub fn get_context_value(type_values: &TypeValues) -> ValueResult {
734    type_values
735        .get_type_value(
736            &Value::new(PlaceholderContext::default()),
737            ENVIRONMENT_CONTEXT_SYMBOL,
738        )
739        .ok_or_else(|| {
740            ValueError::from(RuntimeError {
741                code: "STARLARK_BUILD_CONTEXT",
742                message: "Unable to resolve context (this should never happen)".to_string(),
743                label: "".to_string(),
744            })
745        })
746}
747
748/// print(*args)
749fn starlark_print(args: &[Value]) -> ValueResult {
750    let mut parts = Vec::new();
751    let mut first = true;
752    for arg in args {
753        if !first {
754            parts.push(" ".to_string());
755        }
756        first = false;
757        parts.push(arg.to_string());
758    }
759
760    warn!("{}", parts.join(""));
761
762    Ok(Value::new(NoneType::None))
763}
764
765/// register_target(target, callable, depends=None, default=false)
766fn starlark_register_target(
767    type_values: &TypeValues,
768    target: String,
769    callable: Value,
770    depends: Value,
771    default: bool,
772    default_build_script: bool,
773) -> ValueResult {
774    required_type_arg("callable", "function", &callable)?;
775    optional_list_arg("depends", "string", &depends)?;
776
777    let depends = match depends.get_type() {
778        "list" => depends.iter()?.iter().map(|x| x.to_string()).collect(),
779        _ => Vec::new(),
780    };
781
782    let raw_context = get_context_value(type_values)?;
783    let mut context = raw_context
784        .downcast_mut::<EnvironmentContext>()?
785        .ok_or(ValueError::IncorrectParameterType)?;
786
787    context.register_target(target, callable, depends, default, default_build_script);
788
789    Ok(Value::new(NoneType::None))
790}
791
792/// resolve_target(target)
793///
794/// This will return a Value returned from the called function.
795///
796/// If the target is already resolved, its cached return value is returned
797/// immediately.
798///
799/// If the target depends on other targets, those targets will be resolved
800/// recursively before calling the target's function.
801fn starlark_resolve_target(
802    type_values: &TypeValues,
803    call_stack: &mut CallStack,
804    target: String,
805) -> ValueResult {
806    // The block is here so the borrowed `EnvironmentContext` goes out of
807    // scope before we call into another Starlark function. Without this, we
808    // could get a double borrow.
809    let target_entry = {
810        let raw_context = get_context_value(type_values)?;
811        let context = raw_context
812            .downcast_ref::<EnvironmentContext>()
813            .ok_or(ValueError::IncorrectParameterType)?;
814
815        // If we have a resolved value for this target, return it.
816        if let Some(v) = if let Some(t) = context.get_target(&target) {
817            t.resolved_value.as_ref().cloned()
818        } else {
819            None
820        } {
821            return Ok(v);
822        }
823
824        warn!("resolving target {}", target);
825
826        match context.get_target(&target) {
827            Some(v) => Ok((*v).clone()),
828            None => Err(ValueError::from(RuntimeError {
829                code: "BUILD_TARGETS",
830                message: format!("target {} does not exist", target),
831                label: "resolve_target()".to_string(),
832            })),
833        }?
834    };
835
836    // Resolve target dependencies.
837    let mut args = Vec::new();
838
839    for depend_target in target_entry.depends {
840        args.push(starlark_resolve_target(
841            type_values,
842            call_stack,
843            depend_target,
844        )?);
845    }
846
847    let res = target_entry.callable.call(
848        call_stack,
849        type_values,
850        args,
851        LinkedHashMap::new(),
852        None,
853        None,
854    )?;
855
856    // TODO consider replacing the target's callable with a new function that returns the
857    // resolved value. This will ensure a target function is only ever called once.
858
859    // We can't obtain a mutable reference to the context above because it
860    // would create multiple borrows.
861    let raw_context = get_context_value(type_values)?;
862    let mut context = raw_context
863        .downcast_mut::<EnvironmentContext>()?
864        .ok_or(ValueError::IncorrectParameterType)?;
865
866    if let Some(target_entry) = context.get_target_mut(&target) {
867        target_entry.resolved_value = Some(res.clone());
868    }
869
870    Ok(res)
871}
872
873/// resolve_targets()
874fn starlark_resolve_targets(type_values: &TypeValues, call_stack: &mut CallStack) -> ValueResult {
875    let resolve_target_fn = type_values
876        .get_type_value(&Value::new(PlaceholderContext::default()), "resolve_target")
877        .ok_or_else(|| {
878            ValueError::from(RuntimeError {
879                code: "BUILD_TARGETS",
880                message: "could not find resolve_target() function (this should never happen)"
881                    .to_string(),
882                label: "resolve_targets()".to_string(),
883            })
884        })?;
885
886    // Limit lifetime of EnvironmentContext borrow to prevent double borrows
887    // due to Starlark calls below.
888    let targets = {
889        let raw_context = get_context_value(type_values)?;
890        let context = raw_context
891            .downcast_ref::<EnvironmentContext>()
892            .ok_or(ValueError::IncorrectParameterType)?;
893
894        let targets = context.targets_to_resolve();
895        warn!("resolving {} targets", targets.len());
896
897        targets
898    };
899
900    for target in targets {
901        resolve_target_fn.call(
902            call_stack,
903            type_values,
904            vec![Value::new(target)],
905            LinkedHashMap::new(),
906            None,
907            None,
908        )?;
909    }
910
911    Ok(Value::new(NoneType::None))
912}
913
914/// set_build_path(path)
915fn starlark_set_build_path(type_values: &TypeValues, path: String) -> ValueResult {
916    let context_value = get_context_value(type_values)?;
917    let mut context = context_value
918        .downcast_mut::<EnvironmentContext>()?
919        .ok_or(ValueError::IncorrectParameterType)?;
920
921    context.set_build_path(&PathBuf::from(&path)).map_err(|e| {
922        ValueError::from(RuntimeError {
923            code: "BUILD_TARGETS",
924            message: e.to_string(),
925            label: "set_build_path()".to_string(),
926        })
927    })?;
928
929    Ok(Value::new(NoneType::None))
930}
931
932starlark_module! { build_targets_module =>
933    print(*args) {
934        starlark_print(&args)
935    }
936
937    register_target(
938        env env,
939        target: String,
940        callable,
941        depends = NoneType::None,
942        default: bool = false,
943        default_build_script: bool = false
944    ) {
945        starlark_register_target(env, target, callable, depends, default, default_build_script)
946    }
947
948    resolve_target(env env, call_stack cs, target: String) {
949        starlark_resolve_target(env, cs, target)
950    }
951
952    resolve_targets(env env, call_stack cs) {
953        starlark_resolve_targets(env, cs)
954    }
955
956    set_build_path(env env, path: String) {
957        starlark_set_build_path(env, path)
958    }
959}
960
961/// Register our Starlark dialect with an environment and type values.
962pub fn register_starlark_dialect(
963    env: &mut Environment,
964    type_values: &mut TypeValues,
965) -> Result<(), EnvironmentError> {
966    build_targets_module(env, type_values);
967
968    Ok(())
969}
970
971/// Populate a Starlark environment with state.
972///
973/// This sets variables (as opposed to type values) and is suitable to call on a child
974/// environment when the parent environment is already frozen.
975pub fn populate_environment(
976    env: &mut Environment,
977    type_values: &mut TypeValues,
978    context: EnvironmentContext,
979) -> Result<(), EnvironmentError> {
980    env.set(ENVIRONMENT_CONTEXT_SYMBOL, Value::new(context))?;
981
982    // We alias various globals as BuildTargets.* attributes so they are
983    // available via the type object API. This is a bit hacky. But it allows
984    // Rust code with only access to the TypeValues dictionary to retrieve
985    // these symbols.
986    for f in &[
987        "register_target",
988        "resolve_target",
989        "resolve_targets",
990        "set_build_path",
991        ENVIRONMENT_CONTEXT_SYMBOL,
992    ] {
993        type_values.add_type_value(PlaceholderContext::TYPE, f, env.get(f)?);
994    }
995
996    Ok(())
997}
998
999/// Build a registered target in a Starlark environment.
1000pub fn build_target(
1001    _env: &mut Environment,
1002    type_values: &TypeValues,
1003    call_stack: &mut CallStack,
1004    target: &str,
1005) -> Result<ResolvedTarget> {
1006    let resolved_value = {
1007        let context_value = get_context_value(type_values)
1008            .map_err(|_| anyhow!("unable to resolve context value"))?;
1009        let context = context_value
1010            .downcast_ref::<EnvironmentContext>()
1011            .ok_or_else(|| anyhow!("context has incorrect type"))?;
1012
1013        let v = if let Some(t) = context.get_target(target) {
1014            if let Some(t) = &t.built_target {
1015                return Ok(t.clone());
1016            }
1017
1018            if let Some(v) = &t.resolved_value {
1019                v.clone()
1020            } else {
1021                return Err(anyhow!("target {} is not resolved", target));
1022            }
1023        } else {
1024            return Err(anyhow!("target {} is not resolved", target));
1025        };
1026
1027        v
1028    };
1029
1030    let build = type_values
1031        .get_type_value(&resolved_value, "build")
1032        .ok_or_else(|| anyhow!("{} does not implement build()", resolved_value.get_type()))?;
1033
1034    let resolved_target_value = build
1035        .call(
1036            call_stack,
1037            type_values,
1038            vec![resolved_value, Value::from(target)],
1039            LinkedHashMap::new(),
1040            None,
1041            None,
1042        )
1043        .map_err(|e| anyhow!("error calling build(): {:?}", e))?;
1044
1045    let resolved_target = resolved_target_value
1046        .downcast_ref::<ResolvedTargetValue>()
1047        .unwrap();
1048
1049    let context_value = get_context_value(type_values)
1050        .map_err(|e| anyhow!("unable to resolve context: {:?}", e))?;
1051    let mut context = context_value
1052        .downcast_mut::<EnvironmentContext>()
1053        .map_err(|_| anyhow!("unable to obtain mutable context"))?
1054        .ok_or_else(|| anyhow!("context has incorrect type"))?;
1055
1056    context.get_target_mut(target).unwrap().built_target = Some(resolved_target.inner.clone());
1057
1058    Ok(resolved_target.inner.clone())
1059}
1060
1061/// Runs a named target.
1062///
1063/// Runs the default target is a target name is not specified.
1064pub fn run_target(
1065    env: &mut Environment,
1066    type_values: &TypeValues,
1067    call_stack: &mut CallStack,
1068    target: Option<&str>,
1069) -> Result<()> {
1070    let target = {
1071        // Block to avoid nested borrow.
1072        let context_value = get_context_value(type_values)
1073            .map_err(|e| anyhow!("unable to resolve context value: {:?}", e))?;
1074        let context = context_value.downcast_ref::<EnvironmentContext>().unwrap();
1075
1076        if let Some(t) = target {
1077            t.to_string()
1078        } else if let Some(t) = context.default_target() {
1079            t.to_string()
1080        } else {
1081            return Err(anyhow!("unable to determine target to run"));
1082        }
1083    };
1084
1085    let resolved_target = build_target(env, type_values, call_stack, &target)?;
1086
1087    resolved_target.run()
1088}
1089
1090#[cfg(test)]
1091mod test {
1092    use super::*;
1093    use crate::testutil::*;
1094
1095    #[test]
1096    fn test_register_target() -> Result<()> {
1097        let mut env = StarlarkEnvironment::new()?;
1098        env.eval("def foo(): pass")?;
1099        env.eval("register_target('default', foo)")?;
1100
1101        let context_value = get_context_value(&env.type_values).unwrap();
1102        let context = context_value
1103            .downcast_ref::<EnvironmentContext>()
1104            .ok_or(ValueError::IncorrectParameterType)
1105            .unwrap();
1106
1107        assert_eq!(context.targets().len(), 1);
1108        assert!(context.get_target("default").is_some());
1109        assert_eq!(
1110            context.get_target("default").unwrap().callable.to_string(),
1111            "foo()".to_string()
1112        );
1113        assert_eq!(context.targets_order(), &vec!["default".to_string()]);
1114        assert_eq!(context.default_target(), Some("default"));
1115
1116        Ok(())
1117    }
1118
1119    #[test]
1120    fn test_register_target_multiple() -> Result<()> {
1121        let mut env = StarlarkEnvironment::new()?;
1122        env.eval("def foo(): pass")?;
1123        env.eval("def bar(): pass")?;
1124        env.eval("register_target('foo', foo)")?;
1125        env.eval("register_target('bar', bar, depends=['foo'], default=True)")?;
1126
1127        let context_value = get_context_value(&env.type_values).unwrap();
1128        let context = context_value
1129            .downcast_ref::<EnvironmentContext>()
1130            .ok_or(ValueError::IncorrectParameterType)
1131            .unwrap();
1132
1133        assert_eq!(context.targets().len(), 2);
1134        assert_eq!(context.default_target(), Some("bar"));
1135        assert_eq!(
1136            &context.get_target("bar").unwrap().depends,
1137            &vec!["foo".to_string()],
1138        );
1139
1140        Ok(())
1141    }
1142}