<commit_before>use codegen::{ClassAdded, ClassCodegen, Configure, EnumAdded, EnumCodegen, InterfaceAdded,
              InterfaceCodegen, TupleAdded, TupleCodegen, TypeField, TypeFieldAdded,
              TypeFieldCodegen};
use core::RpSubTypeStrategy;
use core::errors::Result;
use genco::csharp::{using, Argument};
use genco::{Cons, Csharp, Element, IntoTokens, Quoted, Tokens};
use std::rc::Rc;

pub struct Module;

impl Module {
    pub fn initialize(self, e: Configure) {
        let json_net = Rc::new(JsonNet::new());

        e.options
            .class_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .enum_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .interface_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .type_field_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .tuple_generators
            .push(Box::new(Rc::clone(&json_net)));
    }
}

/// Apply attributes.
struct JsonNet {
    object: Csharp<'static>,
    type_: Csharp<'static>,
    invalid_operation: Csharp<'static>,
    enumerator: Csharp<'static>,
    j_array: Csharp<'static>,
    j_token: Csharp<'static>,
    json_reader: Csharp<'static>,
    json_writer: Csharp<'static>,
    json_serializer: Csharp<'static>,
}

impl JsonNet {
    pub fn new() -> Self {
        Self {
            object: using("System", "Object"),
            type_: using("System", "Type").qualified(),
            invalid_operation: using("System", "InvalidOperationException"),
            enumerator: using("System.Collections.Generic", "IEnumerator"),
            j_array: using("Newtonsoft.Json.Linq", "JArray"),
            j_token: using("Newtonsoft.Json.Linq", "JToken"),
            json_reader: using("Newtonsoft.Json", "JsonReader"),
            json_writer: using("Newtonsoft.Json", "JsonWriter"),
            json_serializer: using("Newtonsoft.Json", "JsonSerializer"),
        }
    }
}

impl ClassCodegen for JsonNet {
    fn generate(&self, e: ClassAdded) -> Result<()> {
        let mut type_field = e.type_field;
        let names = &e.names;
        let spec = e.spec;
        let fields = e.fields;

        spec.attribute(JsonObject);

        // Annotate all constructors.
        for c in &mut spec.constructors {
            c.attribute(JsonConstructor);

            for (field, (argument, name)) in
                fields.iter().zip(c.arguments.iter_mut().zip(names.iter()))
            {
                let required = if field.optional {
                    Required::Default
                } else {
                    Required::DisallowNull
                };

                argument.attribute(JsonProperty(name.clone(), required));
            }

            // Modify the class to deserialize, and pass type field into the super class.
            if let Some(&mut TypeField {
                ref mut field,
                ref tag,
            }) = type_field.as_mut()
            {
                let mut a = Argument::new(field.ty(), field.var());
                a.attribute(JsonProperty(tag.clone(), Required::DisallowNull));
                c.arguments.insert(0, a);
                c.base = Some(toks!["base(", field.var(), ")"]);
            }
        }

        // Add field attribute.
        for (field, (spec, name)) in fields.iter().zip(spec.fields.iter_mut().zip(names.iter())) {
            let required = if field.optional {
                Required::Default
            } else {
                Required::DisallowNull
            };

            spec.attribute(JsonProperty(name.clone(), required));
        }

        Ok(())
    }
}

impl EnumCodegen for JsonNet {
    fn generate(&self, e: EnumAdded) -> Result<()> {
        let spec = e.spec;
        let names = e.names;

        spec.attribute(StringEnumConverter);

        let mut variants = Tokens::new();

        for (v, name) in spec.variants.clone().into_iter().zip(names.iter().cloned()) {
            let mut annotated = Tokens::new();
            annotated.push(EnumMember(name));
            annotated.push(v);
            variants.push(annotated);
        }

        spec.variants = variants;
        Ok(())
    }
}

impl InterfaceCodegen for JsonNet {
    fn generate(&self, InterfaceAdded { spec, body, .. }: InterfaceAdded) -> Result<()> {
        match body.sub_type_strategy {
            RpSubTypeStrategy::Tagged { ref tag, .. } => {
                let tag = Rc::new(tag.to_string()).into();
                spec.attribute(JsonSubTypes(tag));
            }
        }

        for sub_type in &body.sub_types {
            let v = toks![spec.name(), ".", sub_type.ident.as_str()];
            spec.attribute(JsonSubType(sub_type.name().into(), v));
        }

        return Ok(());

        struct JsonSubTypes<'el>(Cons<'el>);

        impl<'el> IntoTokens<'el, Csharp<'el>> for JsonSubTypes<'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let converter = using("Newtonsoft.Json", "JsonConverter");
                let sub_types = using("JsonSubTypes", "JsonSubtypes");

                let mut args = Tokens::new();
                args.append(toks!["typeof(", sub_types, ")"]);
                args.append(Element::from(self.0.quoted()));

                toks!["[", converter, "(", args.join(", "), ")]"]
            }
        }

        struct JsonSubType<'el>(Cons<'el>, Tokens<'el, Csharp<'el>>);

        impl<'el> IntoTokens<'el, Csharp<'el>> for JsonSubType<'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let sub_types = using("JsonSubTypes", "JsonSubtypes");

                let mut args = Tokens::new();
                args.append(toks!["typeof(", self.1, ")"]);
                args.append(Element::from(self.0.quoted()));

                toks!["[", sub_types, ".KnownSubType(", args.join(", "), ")]"]
            }
        }
    }
}

impl TypeFieldCodegen for JsonNet {
    fn generate(&self, TypeFieldAdded { field, tag }: TypeFieldAdded) -> Result<()> {
        field.attribute(JsonProperty(tag.clone(), Required::DisallowNull));
        Ok(())
    }
}

impl TupleCodegen for JsonNet {
    fn generate(&self, TupleAdded { mut spec }: TupleAdded) -> Result<()> {
        use genco::csharp::{local, Class, Method, Modifier, BOOLEAN};

        let converter = Rc::new(format!("{}.Json_Net_Converter", spec.name().as_ref()));
        spec.attribute(JsonConverter(local(converter)));

        let body = {
            let mut c = Class::new("Json_Net_Converter");
            let converter = using("Newtonsoft.Json", "JsonConverter");
            c.implements = vec![converter];

            c.body.push(CanConvert(self, &spec));
            c.body.push(WriteJson(self, &mut spec));
            c.body.push(ReadJson(self, &spec));

            c
        };

        // Converter for this tuple.
        spec.body.push(body);

        return Ok(());

        #[allow(unused)]
        // public override bool CanConvert impl
        struct CanConvert<'a, 'el: 'a>(&'a JsonNet, &'a Class<'el>);

        impl<'a, 'el> IntoTokens<'el, Csharp<'el>> for CanConvert<'a, 'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let cls = local(self.1.name());

                let mut can_convert = Method::new("CanConvert");
                can_convert
                    .arguments
                    .push(Argument::new(self.0.type_.clone(), "objectType"));
                can_convert.modifiers = vec![Modifier::Public, Modifier::Override];
                can_convert.returns = BOOLEAN;

                can_convert.body.push({
                    let ck = toks!["objectType == typeof(", cls.clone(), ")"];
                    toks!["return ", ck, ";"]
                });

                can_convert.into_tokens()
            }
        }

        // public override bool WriteJson impl
        struct WriteJson<'a, 'el: 'a>(&'a JsonNet, &'a Class<'el>);

        impl<'a, 'el> IntoTokens<'el, Csharp<'el>> for WriteJson<'a, 'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let mut read_json = Method::new("WriteJson");
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_writer.clone(), "writer"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.object.clone(), "obj"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_serializer.clone(), "serializer"));
                read_json.modifiers = vec![Modifier::Public, Modifier::Override];

                read_json.body.push({
                    let mut t = Tokens::new();

                    let ty = local(self.1.name());

                    t.push(toks![ty.clone(), " o = (", ty.clone(), ")obj;"]);

                    t.push(toks![
                        self.0.j_array.clone(),
                        " array = new ",
                        self.0.j_array.clone(),
                        "();",
                    ]);

                    for f in &self.1.fields {
                        let mut ser = toks![
                            self.0.j_token.clone(),
                            ".FromObject(o.",
                            f.var(),
                            ", serializer)",
                        ];

                        t.push(toks!["array.Add(", ser, ");"]);
                    }

                    t.push("array.WriteTo(writer);");

                    t
                });

                read_json.into_tokens()
            }
        }

        // public override bool ReadJson impl
        struct ReadJson<'a, 'el: 'a>(&'a JsonNet, &'a Class<'el>);

        impl<'a, 'el> IntoTokens<'el, Csharp<'el>> for ReadJson<'a, 'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let cls = local(self.1.name());

                let mut read_json = Method::new("ReadJson");
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_reader.clone(), "reader"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.type_.clone(), "objectType"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.object.clone(), "existingValue"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_serializer.clone(), "serializer"));
                read_json.modifiers = vec![Modifier::Public, Modifier::Override];
                read_json.returns = self.0.object.clone();

                read_json.body.push(toks![
                    self.0.j_array.clone(),
                    " array = ",
                    self.0.j_array.clone(),
                    ".Load(reader);",
                ]);

                read_json.body.push(toks![
                    self.0
                        .enumerator
                        .with_arguments(vec![self.0.j_token.clone()]),
                    " enumerator = array.GetEnumerator();",
                ]);

                let mut args = Tokens::new();

                for f in &self.1.fields {
                    read_json.body.push("if (!enumerator.MoveNext()) {");
                    let msg = "expected more items in array";
                    read_json.body.nested(toks![
                        "throw new ",
                        self.0.invalid_operation.clone(),
                        "(",
                        msg.quoted(),
                        ");",
                    ]);
                    read_json.body.push("}");

                    let to_object = toks!["enumerator.Current.ToObject<", f.ty(), ">(serializer);"];

                    read_json
                        .body
                        .push(toks![f.ty(), " ", f.var(), " = ", to_object, ";",]);

                    args.append(f.var());
                }

                read_json.body.push(toks![
                    "return new ",
                    cls.clone(),
                    "(",
                    args.join(", "),
                    ");",
                ]);

                read_json.into_tokens()
            }
        }
    }
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
enum Required {
    Default,
    Always,
    AllowNull,
    DisallowNull,
}

impl<'el> IntoTokens<'el, Csharp<'el>> for Required {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let required = using("Newtonsoft.Json", "Required");

        let out = match self {
            Required::Default => "Default",
            Required::Always => "Always",
            Required::AllowNull => "AllowNull",
            Required::DisallowNull => "DisallowNull",
        };

        toks![required, ".", out]
    }
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
enum NullValueHandling {
    Include,
    Ignore,
}

impl<'el> IntoTokens<'el, Csharp<'el>> for NullValueHandling {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let null_value_handling = using("Newtonsoft.Json", "NullValueHandling");

        let out = match self {
            NullValueHandling::Include => "Include",
            NullValueHandling::Ignore => "Ignore",
        };

        toks![null_value_handling, ".", out]
    }
}

/// [JsonObject(..)] attribute
pub struct JsonObject;

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonObject {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let object = using("Newtonsoft.Json", "JsonObject");

        let mut args: Tokens<'el, Csharp<'el>> = Tokens::new();

        args.append(toks![
            "ItemNullValueHandling = ",
            NullValueHandling::Ignore.into_tokens(),
        ]);

        toks!["[", object, "(", args.join(", "), ")]"]
    }
}

/// [JsonProperty(..)] attribute
pub struct JsonProperty<'el>(Cons<'el>, Required);

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonProperty<'el> {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let property = using("Newtonsoft.Json", "JsonProperty");

        let mut args: Tokens<'el, Csharp<'el>> = Tokens::new();

        args.append(self.0.quoted());

        match self.1 {
            Required::Default => {}
            other => args.append(toks!["Required = ", other.into_tokens()]),
        }

        toks!["[", property, "(", args.join(", "), ")]"]
    }
}

/// [JsonConstructor] attribute
pub struct JsonConstructor;

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonConstructor {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let constructor = using("Newtonsoft.Json", "JsonConstructor");
        toks!["[", constructor, "]"]
    }
}

/// [EnumMember(..)] attribute
pub struct EnumMember<'el>(Cons<'el>);

impl<'el> IntoTokens<'el, Csharp<'el>> for EnumMember<'el> {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let enum_member = using("System.Runtime.Serialization", "EnumMember");

        let mut args: Tokens<'el, Csharp<'el>> = Tokens::new();
        args.append(toks!["Value = ", self.0.quoted()]);
        toks!["[", enum_member, "(", args.join(", "), ")]"]
    }
}

/// [EnumAttribute(..)] attribute
pub struct StringEnumConverter;

impl<'el> IntoTokens<'el, Csharp<'el>> for StringEnumConverter {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        JsonConverter(using("Newtonsoft.Json.Converters", "StringEnumConverter")).into_tokens()
    }
}

/// [JsonCoverter(..)] attribute
pub struct JsonConverter<'el>(Csharp<'el>);

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonConverter<'el> {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let converter = using("Newtonsoft.Json", "JsonConverter");

        toks!["[", converter, "(typeof(", self.0, "))]"]
    }
}
<commit_msg>csharp: spec does not need to be mutable<commit_after>use codegen::{ClassAdded, ClassCodegen, Configure, EnumAdded, EnumCodegen, InterfaceAdded,
              InterfaceCodegen, TupleAdded, TupleCodegen, TypeField, TypeFieldAdded,
              TypeFieldCodegen};
use core::RpSubTypeStrategy;
use core::errors::Result;
use genco::csharp::{using, Argument};
use genco::{Cons, Csharp, Element, IntoTokens, Quoted, Tokens};
use std::rc::Rc;

pub struct Module;

impl Module {
    pub fn initialize(self, e: Configure) {
        let json_net = Rc::new(JsonNet::new());

        e.options
            .class_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .enum_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .interface_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .type_field_generators
            .push(Box::new(Rc::clone(&json_net)));

        e.options
            .tuple_generators
            .push(Box::new(Rc::clone(&json_net)));
    }
}

/// Apply attributes.
struct JsonNet {
    object: Csharp<'static>,
    type_: Csharp<'static>,
    invalid_operation: Csharp<'static>,
    enumerator: Csharp<'static>,
    j_array: Csharp<'static>,
    j_token: Csharp<'static>,
    json_reader: Csharp<'static>,
    json_writer: Csharp<'static>,
    json_serializer: Csharp<'static>,
}

impl JsonNet {
    pub fn new() -> Self {
        Self {
            object: using("System", "Object"),
            type_: using("System", "Type").qualified(),
            invalid_operation: using("System", "InvalidOperationException"),
            enumerator: using("System.Collections.Generic", "IEnumerator"),
            j_array: using("Newtonsoft.Json.Linq", "JArray"),
            j_token: using("Newtonsoft.Json.Linq", "JToken"),
            json_reader: using("Newtonsoft.Json", "JsonReader"),
            json_writer: using("Newtonsoft.Json", "JsonWriter"),
            json_serializer: using("Newtonsoft.Json", "JsonSerializer"),
        }
    }
}

impl ClassCodegen for JsonNet {
    fn generate(&self, e: ClassAdded) -> Result<()> {
        let mut type_field = e.type_field;
        let names = &e.names;
        let spec = e.spec;
        let fields = e.fields;

        spec.attribute(JsonObject);

        // Annotate all constructors.
        for c in &mut spec.constructors {
            c.attribute(JsonConstructor);

            for (field, (argument, name)) in
                fields.iter().zip(c.arguments.iter_mut().zip(names.iter()))
            {
                let required = if field.optional {
                    Required::Default
                } else {
                    Required::DisallowNull
                };

                argument.attribute(JsonProperty(name.clone(), required));
            }

            // Modify the class to deserialize, and pass type field into the super class.
            if let Some(&mut TypeField {
                ref mut field,
                ref tag,
            }) = type_field.as_mut()
            {
                let mut a = Argument::new(field.ty(), field.var());
                a.attribute(JsonProperty(tag.clone(), Required::DisallowNull));
                c.arguments.insert(0, a);
                c.base = Some(toks!["base(", field.var(), ")"]);
            }
        }

        // Add field attribute.
        for (field, (spec, name)) in fields.iter().zip(spec.fields.iter_mut().zip(names.iter())) {
            let required = if field.optional {
                Required::Default
            } else {
                Required::DisallowNull
            };

            spec.attribute(JsonProperty(name.clone(), required));
        }

        Ok(())
    }
}

impl EnumCodegen for JsonNet {
    fn generate(&self, e: EnumAdded) -> Result<()> {
        let spec = e.spec;
        let names = e.names;

        spec.attribute(StringEnumConverter);

        let mut variants = Tokens::new();

        for (v, name) in spec.variants.clone().into_iter().zip(names.iter().cloned()) {
            let mut annotated = Tokens::new();
            annotated.push(EnumMember(name));
            annotated.push(v);
            variants.push(annotated);
        }

        spec.variants = variants;
        Ok(())
    }
}

impl InterfaceCodegen for JsonNet {
    fn generate(&self, InterfaceAdded { spec, body, .. }: InterfaceAdded) -> Result<()> {
        match body.sub_type_strategy {
            RpSubTypeStrategy::Tagged { ref tag, .. } => {
                let tag = Rc::new(tag.to_string()).into();
                spec.attribute(JsonSubTypes(tag));
            }
        }

        for sub_type in &body.sub_types {
            let v = toks![spec.name(), ".", sub_type.ident.as_str()];
            spec.attribute(JsonSubType(sub_type.name().into(), v));
        }

        return Ok(());

        struct JsonSubTypes<'el>(Cons<'el>);

        impl<'el> IntoTokens<'el, Csharp<'el>> for JsonSubTypes<'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let converter = using("Newtonsoft.Json", "JsonConverter");
                let sub_types = using("JsonSubTypes", "JsonSubtypes");

                let mut args = Tokens::new();
                args.append(toks!["typeof(", sub_types, ")"]);
                args.append(Element::from(self.0.quoted()));

                toks!["[", converter, "(", args.join(", "), ")]"]
            }
        }

        struct JsonSubType<'el>(Cons<'el>, Tokens<'el, Csharp<'el>>);

        impl<'el> IntoTokens<'el, Csharp<'el>> for JsonSubType<'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let sub_types = using("JsonSubTypes", "JsonSubtypes");

                let mut args = Tokens::new();
                args.append(toks!["typeof(", self.1, ")"]);
                args.append(Element::from(self.0.quoted()));

                toks!["[", sub_types, ".KnownSubType(", args.join(", "), ")]"]
            }
        }
    }
}

impl TypeFieldCodegen for JsonNet {
    fn generate(&self, TypeFieldAdded { field, tag }: TypeFieldAdded) -> Result<()> {
        field.attribute(JsonProperty(tag.clone(), Required::DisallowNull));
        Ok(())
    }
}

impl TupleCodegen for JsonNet {
    fn generate(&self, TupleAdded { spec }: TupleAdded) -> Result<()> {
        use genco::csharp::{local, Class, Method, Modifier, BOOLEAN};

        let converter = Rc::new(format!("{}.Json_Net_Converter", spec.name().as_ref()));
        spec.attribute(JsonConverter(local(converter)));

        let body = {
            let mut c = Class::new("Json_Net_Converter");
            let converter = using("Newtonsoft.Json", "JsonConverter");
            c.implements = vec![converter];

            c.body.push(CanConvert(self, &spec));
            c.body.push(WriteJson(self, &spec));
            c.body.push(ReadJson(self, &spec));

            c
        };

        // Converter for this tuple.
        spec.body.push(body);

        return Ok(());

        #[allow(unused)]
        // public override bool CanConvert impl
        struct CanConvert<'a, 'el: 'a>(&'a JsonNet, &'a Class<'el>);

        impl<'a, 'el> IntoTokens<'el, Csharp<'el>> for CanConvert<'a, 'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let cls = local(self.1.name());

                let mut can_convert = Method::new("CanConvert");
                can_convert
                    .arguments
                    .push(Argument::new(self.0.type_.clone(), "objectType"));
                can_convert.modifiers = vec![Modifier::Public, Modifier::Override];
                can_convert.returns = BOOLEAN;

                can_convert.body.push({
                    let ck = toks!["objectType == typeof(", cls.clone(), ")"];
                    toks!["return ", ck, ";"]
                });

                can_convert.into_tokens()
            }
        }

        // public override bool WriteJson impl
        struct WriteJson<'a, 'el: 'a>(&'a JsonNet, &'a Class<'el>);

        impl<'a, 'el> IntoTokens<'el, Csharp<'el>> for WriteJson<'a, 'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let mut read_json = Method::new("WriteJson");
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_writer.clone(), "writer"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.object.clone(), "obj"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_serializer.clone(), "serializer"));
                read_json.modifiers = vec![Modifier::Public, Modifier::Override];

                read_json.body.push({
                    let mut t = Tokens::new();

                    let ty = local(self.1.name());

                    t.push(toks![ty.clone(), " o = (", ty.clone(), ")obj;"]);

                    t.push(toks![
                        self.0.j_array.clone(),
                        " array = new ",
                        self.0.j_array.clone(),
                        "();",
                    ]);

                    for f in &self.1.fields {
                        let mut ser = toks![
                            self.0.j_token.clone(),
                            ".FromObject(o.",
                            f.var(),
                            ", serializer)",
                        ];

                        t.push(toks!["array.Add(", ser, ");"]);
                    }

                    t.push("array.WriteTo(writer);");

                    t
                });

                read_json.into_tokens()
            }
        }

        // public override bool ReadJson impl
        struct ReadJson<'a, 'el: 'a>(&'a JsonNet, &'a Class<'el>);

        impl<'a, 'el> IntoTokens<'el, Csharp<'el>> for ReadJson<'a, 'el> {
            fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
                let cls = local(self.1.name());

                let mut read_json = Method::new("ReadJson");
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_reader.clone(), "reader"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.type_.clone(), "objectType"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.object.clone(), "existingValue"));
                read_json
                    .arguments
                    .push(Argument::new(self.0.json_serializer.clone(), "serializer"));
                read_json.modifiers = vec![Modifier::Public, Modifier::Override];
                read_json.returns = self.0.object.clone();

                read_json.body.push(toks![
                    self.0.j_array.clone(),
                    " array = ",
                    self.0.j_array.clone(),
                    ".Load(reader);",
                ]);

                read_json.body.push(toks![
                    self.0
                        .enumerator
                        .with_arguments(vec![self.0.j_token.clone()]),
                    " enumerator = array.GetEnumerator();",
                ]);

                let mut args = Tokens::new();

                for f in &self.1.fields {
                    read_json.body.push("if (!enumerator.MoveNext()) {");
                    let msg = "expected more items in array";
                    read_json.body.nested(toks![
                        "throw new ",
                        self.0.invalid_operation.clone(),
                        "(",
                        msg.quoted(),
                        ");",
                    ]);
                    read_json.body.push("}");

                    let to_object = toks!["enumerator.Current.ToObject<", f.ty(), ">(serializer);"];

                    read_json
                        .body
                        .push(toks![f.ty(), " ", f.var(), " = ", to_object, ";",]);

                    args.append(f.var());
                }

                read_json.body.push(toks![
                    "return new ",
                    cls.clone(),
                    "(",
                    args.join(", "),
                    ");",
                ]);

                read_json.into_tokens()
            }
        }
    }
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
enum Required {
    Default,
    Always,
    AllowNull,
    DisallowNull,
}

impl<'el> IntoTokens<'el, Csharp<'el>> for Required {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let required = using("Newtonsoft.Json", "Required");

        let out = match self {
            Required::Default => "Default",
            Required::Always => "Always",
            Required::AllowNull => "AllowNull",
            Required::DisallowNull => "DisallowNull",
        };

        toks![required, ".", out]
    }
}

#[allow(unused)]
#[derive(Clone, Copy, Debug)]
enum NullValueHandling {
    Include,
    Ignore,
}

impl<'el> IntoTokens<'el, Csharp<'el>> for NullValueHandling {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let null_value_handling = using("Newtonsoft.Json", "NullValueHandling");

        let out = match self {
            NullValueHandling::Include => "Include",
            NullValueHandling::Ignore => "Ignore",
        };

        toks![null_value_handling, ".", out]
    }
}

/// [JsonObject(..)] attribute
pub struct JsonObject;

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonObject {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let object = using("Newtonsoft.Json", "JsonObject");

        let mut args: Tokens<'el, Csharp<'el>> = Tokens::new();

        args.append(toks![
            "ItemNullValueHandling = ",
            NullValueHandling::Ignore.into_tokens(),
        ]);

        toks!["[", object, "(", args.join(", "), ")]"]
    }
}

/// [JsonProperty(..)] attribute
pub struct JsonProperty<'el>(Cons<'el>, Required);

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonProperty<'el> {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let property = using("Newtonsoft.Json", "JsonProperty");

        let mut args: Tokens<'el, Csharp<'el>> = Tokens::new();

        args.append(self.0.quoted());

        match self.1 {
            Required::Default => {}
            other => args.append(toks!["Required = ", other.into_tokens()]),
        }

        toks!["[", property, "(", args.join(", "), ")]"]
    }
}

/// [JsonConstructor] attribute
pub struct JsonConstructor;

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonConstructor {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let constructor = using("Newtonsoft.Json", "JsonConstructor");
        toks!["[", constructor, "]"]
    }
}

/// [EnumMember(..)] attribute
pub struct EnumMember<'el>(Cons<'el>);

impl<'el> IntoTokens<'el, Csharp<'el>> for EnumMember<'el> {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let enum_member = using("System.Runtime.Serialization", "EnumMember");

        let mut args: Tokens<'el, Csharp<'el>> = Tokens::new();
        args.append(toks!["Value = ", self.0.quoted()]);
        toks!["[", enum_member, "(", args.join(", "), ")]"]
    }
}

/// [EnumAttribute(..)] attribute
pub struct StringEnumConverter;

impl<'el> IntoTokens<'el, Csharp<'el>> for StringEnumConverter {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        JsonConverter(using("Newtonsoft.Json.Converters", "StringEnumConverter")).into_tokens()
    }
}

/// [JsonCoverter(..)] attribute
pub struct JsonConverter<'el>(Csharp<'el>);

impl<'el> IntoTokens<'el, Csharp<'el>> for JsonConverter<'el> {
    fn into_tokens(self) -> Tokens<'el, Csharp<'el>> {
        let converter = using("Newtonsoft.Json", "JsonConverter");

        toks!["[", converter, "(typeof(", self.0, "))]"]
    }
}
<|endoftext|>


<commit_before>//! EGL-based surface and swapchain.

use crate::{conv, native, GlContainer, PhysicalDevice};
use glow::HasContext;
use hal::{image, window as w};

#[derive(Debug)]
pub struct Swapchain {
    framebuffer: glow::Framebuffer,
    renderbuffer: glow::Renderbuffer,
    /// Extent because the window lies
    extent: w::Extent2D,
    format: native::TextureFormat,
    channel: hal::format::ChannelType,
}

#[derive(Debug)]
pub struct Instance {
    display: egl::Display,
    version: (i32, i32),
    config: egl::Config,
    context: egl::Context,
}

unsafe impl Send for Instance {}
unsafe impl Sync for Instance {}

impl hal::Instance<crate::Backend> for Instance {
    fn create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend> {
        let client_extensions =
            egl::query_string(None, egl::EXTENSIONS).map_err(|_| hal::UnsupportedBackend)?;
        log::info!("Client extensions: {:?}", client_extensions);

        //TODO: requre  `GL_OES_surfaceless_context` or work around it

        #[cfg(feature = "x11")]
        let display = {
            const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
            let x11_display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) };
            assert!(!x11_display.is_null());
            let display_attributes = [egl::ATTRIB_NONE];
            egl::get_platform_display(
                EGL_PLATFORM_X11_KHR,
                x11_display as *mut _,
                &display_attributes,
            )
            .unwrap()
        };
        #[cfg(not(feature = "x11"))]
        let display = egl::get_display(egl::DEFAULT_DISPLAY).unwrap();

        let version = egl::initialize(display).map_err(|_| hal::UnsupportedBackend)?;
        let display_extensions = egl::query_string(Some(display), egl::EXTENSIONS).unwrap();
        log::info!(
            "Display version {:?}, extensions: {:?}",
            version,
            display_extensions
        );
        assert!(version >= (1, 4), "Unable to request GL context");

        //Note: only GLES is supported here. This is required to be able to bind EGL PBuffers to textures.

        //TODO: EGL_SLOW_CONFIG
        let config_attributes = [
            egl::CONFORMANT,
            egl::OPENGL_ES2_BIT,
            egl::RENDERABLE_TYPE,
            egl::OPENGL_ES2_BIT,
            egl::NATIVE_RENDERABLE,
            egl::TRUE as _,
            egl::SURFACE_TYPE,
            egl::WINDOW_BIT,
            egl::NONE,
        ];
        let config = match egl::choose_first_config(display, &config_attributes) {
            Ok(Some(config)) => config,
            Ok(None) => {
                log::warn!("no compatible EGL config found");
                return Err(hal::UnsupportedBackend);
            }
            Err(e) => {
                log::error!("error in choose_first_config: {:?}", e);
                return Err(hal::UnsupportedBackend);
            }
        };

        egl::bind_api(egl::OPENGL_ES_API).unwrap();

        //TODO: make it so `Device` == EGL Context
        let gles_version = (3, 1);
        let mut context_attributes = vec![
            egl::CONTEXT_MAJOR_VERSION,
            gles_version.0,
            egl::CONTEXT_MINOR_VERSION,
            gles_version.1,
        ];
        if cfg!(debug_assertions) {
            context_attributes.push(egl::CONTEXT_OPENGL_DEBUG);
            context_attributes.push(egl::TRUE as _);
        }
        context_attributes.push(egl::NONE as _);
        let context = match egl::create_context(display, config, None, &context_attributes) {
            Ok(context) => context,
            Err(e) => {
                log::warn!("unable to create GLES {:?} context: {:?}", gles_version, e);
                return Err(hal::UnsupportedBackend);
            }
        };

        Ok(Instance {
            display,
            version,
            config,
            context,
        })
    }

    fn enumerate_adapters(&self) -> Vec<hal::adapter::Adapter<crate::Backend>> {
        egl::make_current(self.display, None, None, Some(self.context)).unwrap();
        let gl = unsafe {
            GlContainer::from_fn_proc(|name| egl::get_proc_address(name).unwrap() as *const _)
        };

        // Create physical device
        vec![PhysicalDevice::new_adapter(gl)]
    }

    unsafe fn create_surface(
        &self,
        has_handle: &impl raw_window_handle::HasRawWindowHandle,
    ) -> Result<Surface, w::InitError> {
        use raw_window_handle::RawWindowHandle as Rwh;
        let mut native_window = match has_handle.raw_window_handle() {
            #[cfg(not(target_os = "android"))]
            Rwh::Xlib(handle) => handle.window,
            #[cfg(target_os = "android")]
            Rwh::Android(handle) => handle.a_native_window,
            other => panic!("Unsupported window: {:?}", other),
        };
        let attributes = [
            egl::RENDER_BUFFER as usize,
            egl::SINGLE_BUFFER as usize,
            // Always enable sRGB
            egl::GL_COLORSPACE as usize,
            egl::GL_COLORSPACE_SRGB as usize,
            egl::ATTRIB_NONE,
        ];
        match egl::create_platform_window_surface(
            self.display,
            self.config,
            &mut native_window as *mut _ as *mut _,
            &attributes,
        ) {
            Ok(raw) => Ok(Surface {
                raw,
                display: self.display,
                context: self.context,
                swapchain: None,
            }),
            Err(e) => {
                log::warn!("Error in create_window_surface: {:?}", e);
                Err(w::InitError::UnsupportedWindowHandle)
            }
        }
    }

    unsafe fn destroy_surface(&self, surface: Surface) {
        egl::destroy_surface(self.display, surface.raw).unwrap();
    }
}

impl Drop for Instance {
    fn drop(&mut self) {
        if let Err(e) = egl::destroy_context(self.display, self.context) {
            log::warn!("Error in destroy_context: {:?}", e);
        }
        if let Err(e) = egl::terminate(self.display) {
            log::warn!("Error in terminate: {:?}", e);
        }
    }
}

#[derive(Debug)]
pub struct Surface {
    raw: egl::Surface,
    display: egl::Display,
    context: egl::Context,
    pub(crate) swapchain: Option<Swapchain>,
}

unsafe impl Send for Surface {}
unsafe impl Sync for Surface {}

impl w::PresentationSurface<crate::Backend> for Surface {
    type SwapchainImage = native::SwapchainImage;

    unsafe fn configure_swapchain(
        &mut self,
        device: &crate::Device,
        config: w::SwapchainConfig,
    ) -> Result<(), w::CreationError> {
        self.unconfigure_swapchain(device);

        let desc = conv::describe_format(config.format).unwrap();

        let gl = &device.share.context;
        let renderbuffer = gl.create_renderbuffer().unwrap();
        gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer));
        gl.renderbuffer_storage(
            glow::RENDERBUFFER,
            desc.tex_internal,
            config.extent.width as _,
            config.extent.height as _,
        );
        let framebuffer = gl.create_framebuffer().unwrap();
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer));
        gl.framebuffer_renderbuffer(
            glow::READ_FRAMEBUFFER,
            glow::COLOR_ATTACHMENT0,
            glow::RENDERBUFFER,
            Some(renderbuffer),
        );
        gl.bind_renderbuffer(glow::RENDERBUFFER, None);
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);

        self.swapchain = Some(Swapchain {
            renderbuffer,
            framebuffer,
            extent: config.extent,
            format: desc.tex_internal,
            channel: config.format.base_format().1,
        });

        Ok(())
    }

    unsafe fn unconfigure_swapchain(&mut self, device: &crate::Device) {
        let gl = &device.share.context;
        if let Some(sc) = self.swapchain.take() {
            gl.delete_renderbuffer(sc.renderbuffer);
            gl.delete_framebuffer(sc.framebuffer);
        }
    }

    unsafe fn acquire_image(
        &mut self,
        _timeout_ns: u64,
    ) -> Result<(Self::SwapchainImage, Option<w::Suboptimal>), w::AcquireError> {
        let sc = self.swapchain.as_ref().unwrap();
        let sc_image = native::SwapchainImage::new(sc.renderbuffer, sc.format, sc.channel);
        Ok((sc_image, None))
    }
}

impl w::Surface<crate::Backend> for Surface {
    fn supports_queue_family(&self, _: &crate::QueueFamily) -> bool {
        true
    }

    fn capabilities(&self, _physical_device: &PhysicalDevice) -> w::SurfaceCapabilities {
        w::SurfaceCapabilities {
            present_modes: w::PresentMode::FIFO,                  //TODO
            composite_alpha_modes: w::CompositeAlphaMode::OPAQUE, //TODO
            image_count: 2..=2,
            current_extent: None,
            extents: w::Extent2D {
                width: 4,
                height: 4,
            }..=w::Extent2D {
                width: 4096,
                height: 4096,
            },
            max_image_layers: 1,
            usage: image::Usage::COLOR_ATTACHMENT,
        }
    }

    fn supported_formats(
        &self,
        _physical_device: &PhysicalDevice,
    ) -> Option<Vec<hal::format::Format>> {
        use hal::format::Format;
        Some(vec![Format::Rgba8Srgb, Format::Bgra8Srgb])
    }
}

impl Surface {
    pub(crate) unsafe fn present(
        &mut self,
        _image: native::SwapchainImage,
        gl: &GlContainer,
    ) -> Result<Option<w::Suboptimal>, w::PresentError> {
        let sc = self.swapchain.as_ref().unwrap();

        egl::make_current(
            self.display,
            Some(self.raw),
            Some(self.raw),
            Some(self.context),
        )
        .unwrap();
        gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer));
        gl.blit_framebuffer(
            0,
            0,
            sc.extent.width as _,
            sc.extent.height as _,
            0,
            0,
            sc.extent.width as _,
            sc.extent.height as _,
            glow::COLOR_BUFFER_BIT,
            glow::NEAREST,
        );
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);

        egl::swap_buffers(self.display, self.raw).unwrap();
        egl::make_current(self.display, None, None, Some(self.context)).unwrap();

        Ok(None)
    }
}
<commit_msg>[gl] debug code to print out configuration list<commit_after>//! EGL-based surface and swapchain.

use crate::{conv, native, GlContainer, PhysicalDevice};
use glow::HasContext;
use hal::{image, window as w};

#[derive(Debug)]
pub struct Swapchain {
    framebuffer: glow::Framebuffer,
    renderbuffer: glow::Renderbuffer,
    /// Extent because the window lies
    extent: w::Extent2D,
    format: native::TextureFormat,
    channel: hal::format::ChannelType,
}

#[derive(Debug)]
pub struct Instance {
    display: egl::Display,
    version: (i32, i32),
    config: egl::Config,
    context: egl::Context,
}

unsafe impl Send for Instance {}
unsafe impl Sync for Instance {}

impl hal::Instance<crate::Backend> for Instance {
    fn create(_: &str, _: u32) -> Result<Self, hal::UnsupportedBackend> {
        let client_extensions =
            egl::query_string(None, egl::EXTENSIONS).map_err(|_| hal::UnsupportedBackend)?;
        log::info!("Client extensions: {:?}", client_extensions);

        //TODO: requre  `GL_OES_surfaceless_context` or work around it

        #[cfg(feature = "x11")]
        let display = {
            const EGL_PLATFORM_X11_KHR: u32 = 0x31D5;
            let x11_display = unsafe { x11::xlib::XOpenDisplay(std::ptr::null()) };
            assert!(!x11_display.is_null());
            let display_attributes = [egl::ATTRIB_NONE];
            egl::get_platform_display(
                EGL_PLATFORM_X11_KHR,
                x11_display as *mut _,
                &display_attributes,
            )
            .unwrap()
        };
        #[cfg(not(feature = "x11"))]
        let display = egl::get_display(egl::DEFAULT_DISPLAY).unwrap();

        let version = egl::initialize(display).map_err(|_| hal::UnsupportedBackend)?;
        let display_extensions = egl::query_string(Some(display), egl::EXTENSIONS).unwrap();
        log::info!(
            "Display version {:?}, extensions: {:?}",
            version,
            display_extensions
        );
        assert!(version >= (1, 4), "Unable to request GL context");

        {
            debug!("Configurations:");
            let mut configurations = Vec::with_capacity(100);
            egl::get_configs(display, &mut configurations).unwrap();
            for &config in configurations.iter() {
                debug!("\tCONFORMANT=0x{:X}, RENDERABLE=0x{:X}, NATIVE_RENDERABLE=0x{:X}, SURFACE_TYPE=0x{:X}",
                    egl::get_config_attrib(display, config, egl::CONFORMANT).unwrap(),
                    egl::get_config_attrib(display, config, egl::RENDERABLE_TYPE).unwrap(),
                    egl::get_config_attrib(display, config, egl::NATIVE_RENDERABLE).unwrap(),
                    egl::get_config_attrib(display, config, egl::SURFACE_TYPE).unwrap(),
                );
            }
        }

        //Note: only GLES is supported here.

        //TODO: EGL_SLOW_CONFIG
        let config_attributes = [
            egl::CONFORMANT,
            egl::OPENGL_ES2_BIT,
            egl::RENDERABLE_TYPE,
            egl::OPENGL_ES2_BIT,
            egl::NATIVE_RENDERABLE,
            egl::TRUE as _,
            egl::SURFACE_TYPE,
            egl::WINDOW_BIT,
            egl::NONE,
        ];
        let config = match egl::choose_first_config(display, &config_attributes) {
            Ok(Some(config)) => config,
            Ok(None) => {
                log::warn!("no compatible EGL config found");
                return Err(hal::UnsupportedBackend);
            }
            Err(e) => {
                log::error!("error in choose_first_config: {:?}", e);
                return Err(hal::UnsupportedBackend);
            }
        };

        egl::bind_api(egl::OPENGL_ES_API).unwrap();

        //TODO: make it so `Device` == EGL Context
        let gles_version = (3, 1);
        let mut context_attributes = vec![
            egl::CONTEXT_MAJOR_VERSION,
            gles_version.0,
            egl::CONTEXT_MINOR_VERSION,
            gles_version.1,
        ];
        if cfg!(debug_assertions) {
            context_attributes.push(egl::CONTEXT_OPENGL_DEBUG);
            context_attributes.push(egl::TRUE as _);
        }
        context_attributes.push(egl::NONE as _);
        let context = match egl::create_context(display, config, None, &context_attributes) {
            Ok(context) => context,
            Err(e) => {
                log::warn!("unable to create GLES {:?} context: {:?}", gles_version, e);
                return Err(hal::UnsupportedBackend);
            }
        };

        Ok(Instance {
            display,
            version,
            config,
            context,
        })
    }

    fn enumerate_adapters(&self) -> Vec<hal::adapter::Adapter<crate::Backend>> {
        egl::make_current(self.display, None, None, Some(self.context)).unwrap();
        let gl = unsafe {
            GlContainer::from_fn_proc(|name| egl::get_proc_address(name).unwrap() as *const _)
        };

        // Create physical device
        vec![PhysicalDevice::new_adapter(gl)]
    }

    unsafe fn create_surface(
        &self,
        has_handle: &impl raw_window_handle::HasRawWindowHandle,
    ) -> Result<Surface, w::InitError> {
        use raw_window_handle::RawWindowHandle as Rwh;
        let mut native_window = match has_handle.raw_window_handle() {
            #[cfg(not(target_os = "android"))]
            Rwh::Xlib(handle) => handle.window,
            #[cfg(target_os = "android")]
            Rwh::Android(handle) => handle.a_native_window,
            other => panic!("Unsupported window: {:?}", other),
        };
        let attributes = [
            egl::RENDER_BUFFER as usize,
            egl::SINGLE_BUFFER as usize,
            // Always enable sRGB
            egl::GL_COLORSPACE as usize,
            egl::GL_COLORSPACE_SRGB as usize,
            egl::ATTRIB_NONE,
        ];
        match egl::create_platform_window_surface(
            self.display,
            self.config,
            &mut native_window as *mut _ as *mut _,
            &attributes,
        ) {
            Ok(raw) => Ok(Surface {
                raw,
                display: self.display,
                context: self.context,
                swapchain: None,
            }),
            Err(e) => {
                log::warn!("Error in create_window_surface: {:?}", e);
                Err(w::InitError::UnsupportedWindowHandle)
            }
        }
    }

    unsafe fn destroy_surface(&self, surface: Surface) {
        egl::destroy_surface(self.display, surface.raw).unwrap();
    }
}

impl Drop for Instance {
    fn drop(&mut self) {
        if let Err(e) = egl::destroy_context(self.display, self.context) {
            log::warn!("Error in destroy_context: {:?}", e);
        }
        if let Err(e) = egl::terminate(self.display) {
            log::warn!("Error in terminate: {:?}", e);
        }
    }
}

#[derive(Debug)]
pub struct Surface {
    raw: egl::Surface,
    display: egl::Display,
    context: egl::Context,
    pub(crate) swapchain: Option<Swapchain>,
}

unsafe impl Send for Surface {}
unsafe impl Sync for Surface {}

impl w::PresentationSurface<crate::Backend> for Surface {
    type SwapchainImage = native::SwapchainImage;

    unsafe fn configure_swapchain(
        &mut self,
        device: &crate::Device,
        config: w::SwapchainConfig,
    ) -> Result<(), w::CreationError> {
        self.unconfigure_swapchain(device);

        let desc = conv::describe_format(config.format).unwrap();

        let gl = &device.share.context;
        let renderbuffer = gl.create_renderbuffer().unwrap();
        gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer));
        gl.renderbuffer_storage(
            glow::RENDERBUFFER,
            desc.tex_internal,
            config.extent.width as _,
            config.extent.height as _,
        );
        let framebuffer = gl.create_framebuffer().unwrap();
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer));
        gl.framebuffer_renderbuffer(
            glow::READ_FRAMEBUFFER,
            glow::COLOR_ATTACHMENT0,
            glow::RENDERBUFFER,
            Some(renderbuffer),
        );
        gl.bind_renderbuffer(glow::RENDERBUFFER, None);
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);

        self.swapchain = Some(Swapchain {
            renderbuffer,
            framebuffer,
            extent: config.extent,
            format: desc.tex_internal,
            channel: config.format.base_format().1,
        });

        Ok(())
    }

    unsafe fn unconfigure_swapchain(&mut self, device: &crate::Device) {
        let gl = &device.share.context;
        if let Some(sc) = self.swapchain.take() {
            gl.delete_renderbuffer(sc.renderbuffer);
            gl.delete_framebuffer(sc.framebuffer);
        }
    }

    unsafe fn acquire_image(
        &mut self,
        _timeout_ns: u64,
    ) -> Result<(Self::SwapchainImage, Option<w::Suboptimal>), w::AcquireError> {
        let sc = self.swapchain.as_ref().unwrap();
        let sc_image = native::SwapchainImage::new(sc.renderbuffer, sc.format, sc.channel);
        Ok((sc_image, None))
    }
}

impl w::Surface<crate::Backend> for Surface {
    fn supports_queue_family(&self, _: &crate::QueueFamily) -> bool {
        true
    }

    fn capabilities(&self, _physical_device: &PhysicalDevice) -> w::SurfaceCapabilities {
        w::SurfaceCapabilities {
            present_modes: w::PresentMode::FIFO,                  //TODO
            composite_alpha_modes: w::CompositeAlphaMode::OPAQUE, //TODO
            image_count: 2..=2,
            current_extent: None,
            extents: w::Extent2D {
                width: 4,
                height: 4,
            }..=w::Extent2D {
                width: 4096,
                height: 4096,
            },
            max_image_layers: 1,
            usage: image::Usage::COLOR_ATTACHMENT,
        }
    }

    fn supported_formats(
        &self,
        _physical_device: &PhysicalDevice,
    ) -> Option<Vec<hal::format::Format>> {
        use hal::format::Format;
        Some(vec![Format::Rgba8Srgb, Format::Bgra8Srgb])
    }
}

impl Surface {
    pub(crate) unsafe fn present(
        &mut self,
        _image: native::SwapchainImage,
        gl: &GlContainer,
    ) -> Result<Option<w::Suboptimal>, w::PresentError> {
        let sc = self.swapchain.as_ref().unwrap();

        egl::make_current(
            self.display,
            Some(self.raw),
            Some(self.raw),
            Some(self.context),
        )
        .unwrap();
        gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None);
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer));
        gl.blit_framebuffer(
            0,
            0,
            sc.extent.width as _,
            sc.extent.height as _,
            0,
            0,
            sc.extent.width as _,
            sc.extent.height as _,
            glow::COLOR_BUFFER_BIT,
            glow::NEAREST,
        );
        gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None);

        egl::swap_buffers(self.display, self.raw).unwrap();
        egl::make_current(self.display, None, None, Some(self.context)).unwrap();

        Ok(None)
    }
}
<|endoftext|>


<commit_before>//! Windows CRT definitions

pub type c_char = i8;
pub type c_long = i32;
pub type c_ulong = u32;
pub type wchar_t = u16;

pub type clock_t = i32;

cfg_if! {
    if #[cfg(all(target_arch = "x86", target_env = "gnu"))] {
        pub type time_t = i32;
    } else {
        pub type time_t = i64;
    }
}

pub type off_t = i32;
pub type dev_t = u32;
pub type ino_t = u16;
pub enum timezone {}
pub type time64_t = i64;

s! {
    // note this is the struct called stat64 in Windows. Not stat, nor stati64.
    pub struct stat {
        pub st_dev: dev_t,
        pub st_ino: ino_t,
        pub st_mode: u16,
        pub st_nlink: ::c_short,
        pub st_uid: ::c_short,
        pub st_gid: ::c_short,
        pub st_rdev: dev_t,
        pub st_size: i64,
        pub st_atime: time64_t,
        pub st_mtime: time64_t,
        pub st_ctime: time64_t,
    }

    // note that this is called utimbuf64 in Windows
    pub struct utimbuf {
        pub actime: time64_t,
        pub modtime: time64_t,
    }

    pub struct timeval {
        pub tv_sec: c_long,
        pub tv_usec: c_long,
    }

    pub struct timespec {
        pub tv_sec: time_t,
        pub tv_nsec: c_long,
    }
}

pub const EXIT_FAILURE: ::c_int = 1;
pub const EXIT_SUCCESS: ::c_int = 0;
pub const RAND_MAX: ::c_int = 32767;
pub const EOF: ::c_int = -1;
pub const SEEK_SET: ::c_int = 0;
pub const SEEK_CUR: ::c_int = 1;
pub const SEEK_END: ::c_int = 2;
pub const _IOFBF: ::c_int = 0;
pub const _IONBF: ::c_int = 4;
pub const _IOLBF: ::c_int = 64;
pub const BUFSIZ: ::c_uint = 512;
pub const FOPEN_MAX: ::c_uint = 20;
pub const FILENAME_MAX: ::c_uint = 260;

cfg_if! {
    if #[cfg(all(target_env = "gnu"))] {
        pub const L_tmpnam: ::c_uint = 14;
        pub const TMP_MAX: ::c_uint = 0x7fff;
    } else if #[cfg(all(target_env = "msvc"))] {
        pub const L_tmpnam: ::c_uint = 260;
        pub const TMP_MAX: ::c_uint = 0x7fff_ffff;
    } else {
        // Unknown target_env
    }
}

pub const O_RDONLY: ::c_int = 0;
pub const O_WRONLY: ::c_int = 1;
pub const O_RDWR: ::c_int = 2;
pub const O_APPEND: ::c_int = 8;
pub const O_CREAT: ::c_int = 256;
pub const O_EXCL: ::c_int = 1024;
pub const O_TEXT: ::c_int = 16384;
pub const O_BINARY: ::c_int = 32768;
pub const O_NOINHERIT: ::c_int = 128;
pub const O_TRUNC: ::c_int = 512;
pub const S_IFCHR: ::c_int = 8192;
pub const S_IFDIR: ::c_int = 16384;
pub const S_IFREG: ::c_int = 32768;
pub const S_IFMT: ::c_int = 61440;
pub const S_IEXEC: ::c_int = 64;
pub const S_IWRITE: ::c_int = 128;
pub const S_IREAD: ::c_int = 256;

pub const LC_ALL: ::c_int = 0;
pub const LC_COLLATE: ::c_int = 1;
pub const LC_CTYPE: ::c_int = 2;
pub const LC_MONETARY: ::c_int = 3;
pub const LC_NUMERIC: ::c_int = 4;
pub const LC_TIME: ::c_int = 5;

pub const EPERM: ::c_int = 1;
pub const ENOENT: ::c_int = 2;
pub const ESRCH: ::c_int = 3;
pub const EINTR: ::c_int = 4;
pub const EIO: ::c_int = 5;
pub const ENXIO: ::c_int = 6;
pub const E2BIG: ::c_int = 7;
pub const ENOEXEC: ::c_int = 8;
pub const EBADF: ::c_int = 9;
pub const ECHILD: ::c_int = 10;
pub const EAGAIN: ::c_int = 11;
pub const ENOMEM: ::c_int = 12;
pub const EACCES: ::c_int = 13;
pub const EFAULT: ::c_int = 14;
pub const EBUSY: ::c_int = 16;
pub const EEXIST: ::c_int = 17;
pub const EXDEV: ::c_int = 18;
pub const ENODEV: ::c_int = 19;
pub const ENOTDIR: ::c_int = 20;
pub const EISDIR: ::c_int = 21;
pub const EINVAL: ::c_int = 22;
pub const ENFILE: ::c_int = 23;
pub const EMFILE: ::c_int = 24;
pub const ENOTTY: ::c_int = 25;
pub const EFBIG: ::c_int = 27;
pub const ENOSPC: ::c_int = 28;
pub const ESPIPE: ::c_int = 29;
pub const EROFS: ::c_int = 30;
pub const EMLINK: ::c_int = 31;
pub const EPIPE: ::c_int = 32;
pub const EDOM: ::c_int = 33;
pub const ERANGE: ::c_int = 34;
pub const EDEADLK: ::c_int = 36;
pub const EDEADLOCK: ::c_int = 36;
pub const ENAMETOOLONG: ::c_int = 38;
pub const ENOLCK: ::c_int = 39;
pub const ENOSYS: ::c_int = 40;
pub const ENOTEMPTY: ::c_int = 41;
pub const EILSEQ: ::c_int = 42;
pub const STRUNCATE: ::c_int = 80;

#[cfg(all(target_env = "msvc", stdbuild))] // " if " -- appease style checker
#[link(name = "msvcrt", cfg(not(target_feature = "crt-static")))]
#[link(name = "libcmt", cfg(target_feature = "crt-static"))]
extern {}

extern {
    #[link_name = "_chmod"]
    pub fn chmod(path: *const c_char, mode: ::c_int) -> ::c_int;
    #[link_name = "_wchmod"]
    pub fn wchmod(path: *const wchar_t, mode: ::c_int) -> ::c_int;
    #[link_name = "_mkdir"]
    pub fn mkdir(path: *const c_char) -> ::c_int;
    #[link_name = "_wrmdir"]
    pub fn wrmdir(path: *const wchar_t) -> ::c_int;
    #[link_name = "_fstat64"]
    pub fn fstat(fildes: ::c_int, buf: *mut stat) -> ::c_int;
    #[link_name = "_stat64"]
    pub fn stat(path: *const c_char, buf: *mut stat) -> ::c_int;
    #[link_name = "_wstat64"]
    pub fn wstat(path: *const wchar_t, buf: *mut stat) -> ::c_int;
    #[link_name = "_wutime64"]
    pub fn wutime(file: *const wchar_t, buf: *mut utimbuf) -> ::c_int;
    #[link_name = "_popen"]
    pub fn popen(command: *const c_char, mode: *const c_char) -> *mut ::FILE;
    #[link_name = "_pclose"]
    pub fn pclose(stream: *mut ::FILE) -> ::c_int;
    #[link_name = "_fdopen"]
    pub fn fdopen(fd: ::c_int, mode: *const c_char) -> *mut ::FILE;
    #[link_name = "_fileno"]
    pub fn fileno(stream: *mut ::FILE) -> ::c_int;
    #[link_name = "_open"]
    pub fn open(path: *const c_char, oflag: ::c_int, ...) -> ::c_int;
    #[link_name = "_wopen"]
    pub fn wopen(path: *const wchar_t, oflag: ::c_int, ...) -> ::c_int;
    #[link_name = "_creat"]
    pub fn creat(path: *const c_char, mode: ::c_int) -> ::c_int;
    #[link_name = "_access"]
    pub fn access(path: *const c_char, amode: ::c_int) -> ::c_int;
    #[link_name = "_chdir"]
    pub fn chdir(dir: *const c_char) -> ::c_int;
    #[link_name = "_close"]
    pub fn close(fd: ::c_int) -> ::c_int;
    #[link_name = "_dup"]
    pub fn dup(fd: ::c_int) -> ::c_int;
    #[link_name = "_dup2"]
    pub fn dup2(src: ::c_int, dst: ::c_int) -> ::c_int;
    #[link_name = "_execv"]
    pub fn execv(prog: *const c_char, argv: *const *const c_char) -> ::intptr_t;
    #[link_name = "_execve"]
    pub fn execve(prog: *const c_char, argv: *const *const c_char,
                  envp: *const *const c_char) -> ::c_int;
    #[link_name = "_execvp"]
    pub fn execvp(c: *const c_char, argv: *const *const c_char) -> ::c_int;
    #[link_name = "_execvpe"]
    pub fn execvpe(c: *const c_char, argv: *const *const c_char,
                   envp: *const *const c_char) -> ::c_int;
    #[link_name = "_getcwd"]
    pub fn getcwd(buf: *mut c_char, size: ::c_int) -> *mut c_char;
    #[link_name = "_getpid"]
    pub fn getpid() -> ::c_int;
    #[link_name = "_isatty"]
    pub fn isatty(fd: ::c_int) -> ::c_int;
    #[link_name = "_lseek"]
    pub fn lseek(fd: ::c_int, offset: c_long, origin: ::c_int) -> c_long;
    #[link_name = "_pipe"]
    pub fn pipe(fds: *mut ::c_int,
                psize: ::c_uint,
                textmode: ::c_int) -> ::c_int;
    #[link_name = "_read"]
    pub fn read(fd: ::c_int, buf: *mut ::c_void, count: ::c_uint) -> ::c_int;
    #[link_name = "_rmdir"]
    pub fn rmdir(path: *const c_char) -> ::c_int;
    #[link_name = "_unlink"]
    pub fn unlink(c: *const c_char) -> ::c_int;
    #[link_name = "_write"]
    pub fn write(fd: ::c_int, buf: *const ::c_void, count: ::c_uint) -> ::c_int;
    #[link_name = "_commit"]
    pub fn commit(fd: ::c_int) -> ::c_int;
    #[link_name = "_get_osfhandle"]
    pub fn get_osfhandle(fd: ::c_int) -> ::intptr_t;
    #[link_name = "_open_osfhandle"]
    pub fn open_osfhandle(osfhandle: ::intptr_t, flags: ::c_int) -> ::c_int;
    pub fn setlocale(category: ::c_int, locale: *const c_char) -> *mut c_char;
    #[link_name = "_wsetlocale"]
    pub fn wsetlocale(category: ::c_int,
                      locale: *const wchar_t) -> *mut wchar_t;
}
<commit_msg>Auto merge of #620 - pornel:master, r=alexcrichton<commit_after>//! Windows CRT definitions

pub type c_char = i8;
pub type c_long = i32;
pub type c_ulong = u32;
pub type wchar_t = u16;

pub type clock_t = i32;

cfg_if! {
    if #[cfg(all(target_arch = "x86", target_env = "gnu"))] {
        pub type time_t = i32;
    } else {
        pub type time_t = i64;
    }
}

pub type off_t = i32;
pub type dev_t = u32;
pub type ino_t = u16;
pub enum timezone {}
pub type time64_t = i64;

s! {
    // note this is the struct called stat64 in Windows. Not stat, nor stati64.
    pub struct stat {
        pub st_dev: dev_t,
        pub st_ino: ino_t,
        pub st_mode: u16,
        pub st_nlink: ::c_short,
        pub st_uid: ::c_short,
        pub st_gid: ::c_short,
        pub st_rdev: dev_t,
        pub st_size: i64,
        pub st_atime: time64_t,
        pub st_mtime: time64_t,
        pub st_ctime: time64_t,
    }

    // note that this is called utimbuf64 in Windows
    pub struct utimbuf {
        pub actime: time64_t,
        pub modtime: time64_t,
    }

    pub struct tm {
        tm_sec: ::c_int,
        tm_min: ::c_int,
        tm_hour: ::c_int,
        tm_mday: ::c_int,
        tm_mon: ::c_int,
        tm_year: ::c_int,
        tm_wday: ::c_int,
        tm_yday: ::c_int,
        tm_isdst: ::c_int,
    }

    pub struct timeval {
        pub tv_sec: c_long,
        pub tv_usec: c_long,
    }

    pub struct timespec {
        pub tv_sec: time_t,
        pub tv_nsec: c_long,
    }
}

pub const EXIT_FAILURE: ::c_int = 1;
pub const EXIT_SUCCESS: ::c_int = 0;
pub const RAND_MAX: ::c_int = 32767;
pub const EOF: ::c_int = -1;
pub const SEEK_SET: ::c_int = 0;
pub const SEEK_CUR: ::c_int = 1;
pub const SEEK_END: ::c_int = 2;
pub const _IOFBF: ::c_int = 0;
pub const _IONBF: ::c_int = 4;
pub const _IOLBF: ::c_int = 64;
pub const BUFSIZ: ::c_uint = 512;
pub const FOPEN_MAX: ::c_uint = 20;
pub const FILENAME_MAX: ::c_uint = 260;

cfg_if! {
    if #[cfg(all(target_env = "gnu"))] {
        pub const L_tmpnam: ::c_uint = 14;
        pub const TMP_MAX: ::c_uint = 0x7fff;
    } else if #[cfg(all(target_env = "msvc"))] {
        pub const L_tmpnam: ::c_uint = 260;
        pub const TMP_MAX: ::c_uint = 0x7fff_ffff;
    } else {
        // Unknown target_env
    }
}

pub const O_RDONLY: ::c_int = 0;
pub const O_WRONLY: ::c_int = 1;
pub const O_RDWR: ::c_int = 2;
pub const O_APPEND: ::c_int = 8;
pub const O_CREAT: ::c_int = 256;
pub const O_EXCL: ::c_int = 1024;
pub const O_TEXT: ::c_int = 16384;
pub const O_BINARY: ::c_int = 32768;
pub const O_NOINHERIT: ::c_int = 128;
pub const O_TRUNC: ::c_int = 512;
pub const S_IFCHR: ::c_int = 8192;
pub const S_IFDIR: ::c_int = 16384;
pub const S_IFREG: ::c_int = 32768;
pub const S_IFMT: ::c_int = 61440;
pub const S_IEXEC: ::c_int = 64;
pub const S_IWRITE: ::c_int = 128;
pub const S_IREAD: ::c_int = 256;

pub const LC_ALL: ::c_int = 0;
pub const LC_COLLATE: ::c_int = 1;
pub const LC_CTYPE: ::c_int = 2;
pub const LC_MONETARY: ::c_int = 3;
pub const LC_NUMERIC: ::c_int = 4;
pub const LC_TIME: ::c_int = 5;

pub const EPERM: ::c_int = 1;
pub const ENOENT: ::c_int = 2;
pub const ESRCH: ::c_int = 3;
pub const EINTR: ::c_int = 4;
pub const EIO: ::c_int = 5;
pub const ENXIO: ::c_int = 6;
pub const E2BIG: ::c_int = 7;
pub const ENOEXEC: ::c_int = 8;
pub const EBADF: ::c_int = 9;
pub const ECHILD: ::c_int = 10;
pub const EAGAIN: ::c_int = 11;
pub const ENOMEM: ::c_int = 12;
pub const EACCES: ::c_int = 13;
pub const EFAULT: ::c_int = 14;
pub const EBUSY: ::c_int = 16;
pub const EEXIST: ::c_int = 17;
pub const EXDEV: ::c_int = 18;
pub const ENODEV: ::c_int = 19;
pub const ENOTDIR: ::c_int = 20;
pub const EISDIR: ::c_int = 21;
pub const EINVAL: ::c_int = 22;
pub const ENFILE: ::c_int = 23;
pub const EMFILE: ::c_int = 24;
pub const ENOTTY: ::c_int = 25;
pub const EFBIG: ::c_int = 27;
pub const ENOSPC: ::c_int = 28;
pub const ESPIPE: ::c_int = 29;
pub const EROFS: ::c_int = 30;
pub const EMLINK: ::c_int = 31;
pub const EPIPE: ::c_int = 32;
pub const EDOM: ::c_int = 33;
pub const ERANGE: ::c_int = 34;
pub const EDEADLK: ::c_int = 36;
pub const EDEADLOCK: ::c_int = 36;
pub const ENAMETOOLONG: ::c_int = 38;
pub const ENOLCK: ::c_int = 39;
pub const ENOSYS: ::c_int = 40;
pub const ENOTEMPTY: ::c_int = 41;
pub const EILSEQ: ::c_int = 42;
pub const STRUNCATE: ::c_int = 80;

#[cfg(all(target_env = "msvc", stdbuild))] // " if " -- appease style checker
#[link(name = "msvcrt", cfg(not(target_feature = "crt-static")))]
#[link(name = "libcmt", cfg(target_feature = "crt-static"))]
extern {}

extern {
    #[link_name = "_chmod"]
    pub fn chmod(path: *const c_char, mode: ::c_int) -> ::c_int;
    #[link_name = "_wchmod"]
    pub fn wchmod(path: *const wchar_t, mode: ::c_int) -> ::c_int;
    #[link_name = "_mkdir"]
    pub fn mkdir(path: *const c_char) -> ::c_int;
    #[link_name = "_wrmdir"]
    pub fn wrmdir(path: *const wchar_t) -> ::c_int;
    #[link_name = "_fstat64"]
    pub fn fstat(fildes: ::c_int, buf: *mut stat) -> ::c_int;
    #[link_name = "_stat64"]
    pub fn stat(path: *const c_char, buf: *mut stat) -> ::c_int;
    #[link_name = "_wstat64"]
    pub fn wstat(path: *const wchar_t, buf: *mut stat) -> ::c_int;
    #[link_name = "_wutime64"]
    pub fn wutime(file: *const wchar_t, buf: *mut utimbuf) -> ::c_int;
    #[link_name = "_popen"]
    pub fn popen(command: *const c_char, mode: *const c_char) -> *mut ::FILE;
    #[link_name = "_pclose"]
    pub fn pclose(stream: *mut ::FILE) -> ::c_int;
    #[link_name = "_fdopen"]
    pub fn fdopen(fd: ::c_int, mode: *const c_char) -> *mut ::FILE;
    #[link_name = "_fileno"]
    pub fn fileno(stream: *mut ::FILE) -> ::c_int;
    #[link_name = "_open"]
    pub fn open(path: *const c_char, oflag: ::c_int, ...) -> ::c_int;
    #[link_name = "_wopen"]
    pub fn wopen(path: *const wchar_t, oflag: ::c_int, ...) -> ::c_int;
    #[link_name = "_creat"]
    pub fn creat(path: *const c_char, mode: ::c_int) -> ::c_int;
    #[link_name = "_access"]
    pub fn access(path: *const c_char, amode: ::c_int) -> ::c_int;
    #[link_name = "_chdir"]
    pub fn chdir(dir: *const c_char) -> ::c_int;
    #[link_name = "_close"]
    pub fn close(fd: ::c_int) -> ::c_int;
    #[link_name = "_dup"]
    pub fn dup(fd: ::c_int) -> ::c_int;
    #[link_name = "_dup2"]
    pub fn dup2(src: ::c_int, dst: ::c_int) -> ::c_int;
    #[link_name = "_execv"]
    pub fn execv(prog: *const c_char, argv: *const *const c_char) -> ::intptr_t;
    #[link_name = "_execve"]
    pub fn execve(prog: *const c_char, argv: *const *const c_char,
                  envp: *const *const c_char) -> ::c_int;
    #[link_name = "_execvp"]
    pub fn execvp(c: *const c_char, argv: *const *const c_char) -> ::c_int;
    #[link_name = "_execvpe"]
    pub fn execvpe(c: *const c_char, argv: *const *const c_char,
                   envp: *const *const c_char) -> ::c_int;
    #[link_name = "_getcwd"]
    pub fn getcwd(buf: *mut c_char, size: ::c_int) -> *mut c_char;
    #[link_name = "_getpid"]
    pub fn getpid() -> ::c_int;
    #[link_name = "_isatty"]
    pub fn isatty(fd: ::c_int) -> ::c_int;
    #[link_name = "_lseek"]
    pub fn lseek(fd: ::c_int, offset: c_long, origin: ::c_int) -> c_long;
    #[link_name = "_pipe"]
    pub fn pipe(fds: *mut ::c_int,
                psize: ::c_uint,
                textmode: ::c_int) -> ::c_int;
    #[link_name = "_read"]
    pub fn read(fd: ::c_int, buf: *mut ::c_void, count: ::c_uint) -> ::c_int;
    #[link_name = "_rmdir"]
    pub fn rmdir(path: *const c_char) -> ::c_int;
    #[link_name = "_unlink"]
    pub fn unlink(c: *const c_char) -> ::c_int;
    #[link_name = "_write"]
    pub fn write(fd: ::c_int, buf: *const ::c_void, count: ::c_uint) -> ::c_int;
    #[link_name = "_commit"]
    pub fn commit(fd: ::c_int) -> ::c_int;
    #[link_name = "_get_osfhandle"]
    pub fn get_osfhandle(fd: ::c_int) -> ::intptr_t;
    #[link_name = "_open_osfhandle"]
    pub fn open_osfhandle(osfhandle: ::intptr_t, flags: ::c_int) -> ::c_int;
    pub fn setlocale(category: ::c_int, locale: *const c_char) -> *mut c_char;
    #[link_name = "_wsetlocale"]
    pub fn wsetlocale(category: ::c_int,
                      locale: *const wchar_t) -> *mut wchar_t;
}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::Write;

use unshare::{Command};
use scan_dir::{self, ScanDir};
use regex::Regex;

use super::super::context::{Context};
use super::super::packages;
use super::generic::{run_command, capture_command};
use builder::error::StepError;
use builder::distrib::Distribution;
use builder::commands::generic::{command, run};
use builder::download;
use config::builders::{ComposerSettings, ComposerDepInfo};
use process_util::capture_stdout;
use file_util;

const DEFAULT_RUNTIME: &'static str = "/usr/bin/php";
const DEFAULT_INCLUDE_PATH: &'static str = ".:/usr/local/lib/composer";

const COMPOSER_HOME: &'static str = "/usr/local/lib/composer";
const COMPOSER_CACHE: &'static str = "/tmp/composer-cache";
const COMPOSER_VENDOR_DIR: &'static str = "/usr/local/lib/composer/vendor";
const COMPOSER_BIN_DIR: &'static str = "/usr/local/bin";
const COMPOSER_BOOTSTRAP: &'static str = "https://getcomposer.org/installer";

impl Default for ComposerSettings {
    fn default() -> Self {
        ComposerSettings {
            install_runtime: true,
            install_dev: false,
            runtime_exe: None,
            include_path: None,
            keep_composer: false,
            vendor_dir: None,
        }
    }
}

fn scan_features(settings: &ComposerSettings)
    -> Vec<packages::Package>
{
    let mut res = vec!();
    res.push(packages::Https);
    res.push(packages::Composer);
    if settings.install_runtime {
        res.push(packages::Php);
        if settings.install_dev {
            res.push(packages::BuildEssential);
            res.push(packages::PhpDev)
        }
    }
    // Probably it's not it worth trying to figure out whether we need Git or Mercurial and it is
    // more likely that a php project is using Git, therefore it is reasonable to simply assume we
    // always need Git
    res.push(packages::Git);
    return res;
}

fn composer_cmd(ctx: &mut Context) -> Result<Command, StepError> {
    let runtime = ctx.composer_settings
        .runtime_exe
        .clone()
        .unwrap_or(DEFAULT_RUNTIME.to_owned());
    let mut cmd = try!(command(ctx, runtime));
    cmd.arg("/usr/local/bin/composer");
    cmd.arg("--no-interaction");
    Ok(cmd)
}

pub fn composer_install(distro: &mut Box<Distribution>, ctx: &mut Context,
    pkgs: &Vec<String>)
    -> Result<(), String>
{
    let features = scan_features(&ctx.composer_settings);
    try!(packages::ensure_packages(distro, ctx, &features));

    if pkgs.len() == 0 {
        return Ok(());
    }

    let mut cmd = try!(composer_cmd(ctx));
    cmd.args(&["global", "require", "--prefer-dist", "--update-no-dev"]);
    cmd.args(pkgs);
    try!(run(cmd));
    Ok(())
}

pub fn composer_dependencies(distro: &mut Box<Distribution>,
    ctx: &mut Context, info: &ComposerDepInfo)
    -> Result<(), StepError>
{
    let features = scan_features(&ctx.composer_settings);
    try!(packages::ensure_packages(distro, ctx, &features));

    let mut cmd = try!(composer_cmd(ctx));
    cmd.arg("install");
    if let Some(ref dir) = info.working_dir {
        cmd.arg(format!("--working-dir={}", dir));
    }
    if !info.dev { cmd.arg("--no-dev"); }
    if info.ignore_platform_reqs { cmd.arg("--ignore-platform-reqs"); }
    if info.no_autoloader { cmd.arg("--no_autoloader"); }
    if info.no_scripts { cmd.arg("--no-scripts"); }
    if info.no_plugins { cmd.arg("--no-plugins"); }
    if info.optimize_autoloader { cmd.arg("--optimize-autoloader"); }

    match info.prefer {
        Some(ref p) if p == "dist" => { cmd.arg("--prefer-dist"); },
        Some(ref p) if p == "source" => { cmd.arg("--prefer-source"); },
        Some(ref p) => return Err(From::from(format!(
            "Value of 'ComposerDependencies.prefer' must be either \
            'source' or 'dist', '{}' given", p
        ))),
        _ => {}
    }

    run(cmd)
}

pub fn configure(ctx: &mut Context) -> Result<(), String> {
    try!(ctx.add_cache_dir(Path::new("/tmp/composer-cache"),
                           "composer-cache".to_string()));

    let vendor_dir = ctx.composer_settings.vendor_dir.as_ref()
        .map(|p| p.to_string_lossy().into_owned())
        .unwrap_or_else(|| COMPOSER_VENDOR_DIR.to_owned());

    ctx.environ.insert("COMPOSER_VENDOR_DIR".to_owned(), vendor_dir);

    ctx.environ.insert("COMPOSER_HOME".to_owned(),
                       COMPOSER_HOME.to_owned());
    ctx.environ.insert("COMPOSER_BIN_DIR".to_owned(),
                       COMPOSER_BIN_DIR.to_owned());
    ctx.environ.insert("COMPOSER_CACHE_DIR".to_owned(),
                       COMPOSER_CACHE.to_owned());
    ctx.environ.insert("COMPOSER_ALLOW_SUPERUSER".to_owned(),
                       "1".to_owned());

    Ok(())
}

pub fn bootstrap(ctx: &mut Context) -> Result<(), String> {
    try_msg!(file_util::create_dir(COMPOSER_HOME, true),
        "Error creating composer home dir {d:?}: {err}", d=COMPOSER_HOME);

    let composer_inst = try!(download::download_file(ctx, COMPOSER_BOOTSTRAP));
    try!(file_util::copy(&composer_inst, &Path::new("/vagga/root/tmp/composer-setup.php"))
        .map_err(|e| format!("Error copying composer installer: {}", e)));

    let runtime_exe = ctx.composer_settings
        .runtime_exe
        .clone()
        .unwrap_or(DEFAULT_RUNTIME.to_owned());

    let args = [
        runtime_exe,
        "/tmp/composer-setup.php".to_owned(),
        "--install-dir=/usr/local/bin/".to_owned(),
        "--filename=composer".to_owned(),
    ];
    try!(run_command(ctx, &args));

    if ctx.composer_settings.install_runtime {
        try!(setup_include_path(ctx));
    }

    Ok(())
}

fn setup_include_path(ctx: &mut Context) -> Result<(), String> {
    let vagga_ini_content = {
        let include_path = ctx.composer_settings
            .include_path.clone()
            .unwrap_or(DEFAULT_INCLUDE_PATH.to_owned());
        format!("include_path={}", include_path)
    };

    let conf_dirs = try_msg!(find_conf_dirs(),
                             "Error listing PHP configuration directories: {err}");

    for conf_d in conf_dirs.iter() {
        // create vagga.ini file
        try!(create_vagga_ini(&conf_d.join("vagga.ini"), &vagga_ini_content));
    }

    if !conf_dirs.is_empty() {
        // vagga.ini file(s) created, we're done here
        return Ok(())
    }

    // If we didn't find any conf.d, ask 'php --ini'
    let conf_d = try!(ask_php_for_conf_d(ctx));

    // create conf.d
    if !conf_d.exists() {
        try!(file_util::create_dir(&conf_d, true)
        .map_err(|e| format!("Error creating directory {:?}: {}", conf_d, e)));
    }
    // create vagga.ini file
    try!(create_vagga_ini(&conf_d, &vagga_ini_content));

    Ok(())
}

fn create_vagga_ini(location: &Path, content: &str) -> Result<(), String> {
    File::create(location)
        .and_then(|mut f| f.write_all(content.as_bytes()))
        .map_err(|e| format!("Error creating file {:?}: {}", location, e))
}

fn find_conf_dirs() -> Result<Vec<PathBuf>, scan_dir::Error> {
    ScanDir::dirs().skip_symlinks(true).read("/vagga/root/etc", |iter| {
        iter.filter(|&(_, ref name)| name.starts_with("php"))
        .flat_map(|(ref entry, _)| {
            ScanDir::dirs().read(entry.path(), |iter| {
                iter.filter(|&(ref entry, ref name)| {
                    name == "conf.d" ||
                    entry.path().join("conf.d").exists()
                })
                .map(|(ref entry, _)| {
                    let path = entry.path();
                    if path.ends_with("conf.d") { path }
                    else { path.join("conf.d") }
                })
                .collect()
            })
        })
        .collect()
    })
}

fn ask_php_for_conf_d(ctx: &mut Context) -> Result<PathBuf, String> {
    let runtime_exe = ctx.composer_settings
        .runtime_exe
        .clone()
        .unwrap_or(DEFAULT_RUNTIME.to_owned());

    let args = [runtime_exe, "--ini".to_owned()];
    let output = try!(capture_command(ctx, &args, &[])
        .and_then(|x| String::from_utf8(x)
            .map_err(|e| format!("Error parsing command output: {}", e)))
        .map_err(|e| format!("Error reading command output: {}", e)));

    // match any line that ends with /etc/php*/**/conf.d, get first result
    let re = Regex::new(r#"(?m).*?(/etc/php\d/.*?conf.d)$"#).expect("Invalid regex");

    let conf_d = try!(re.captures(&output)
        .and_then(|cap| cap.at(1))
        .ok_or("PHP configuration directory was not found".to_owned()));

    Ok(PathBuf::from(conf_d))
}

pub fn finish(ctx: &mut Context) -> Result<(), StepError> {
    try!(list_packages(ctx));
    if !ctx.composer_settings.keep_composer {
        try!(fs::remove_file(Path::new("/vagga/root/usr/local/bin/composer"))
            .map_err(|e| format!("Error removing '/usr/local/bin/composer': {}", e)));
    }

    Ok(())
}

fn list_packages(ctx: &mut Context) -> Result<(), StepError> {
    let mut cmd = try!(composer_cmd(ctx));
    cmd.arg("show");

    try!(capture_stdout(cmd)
        .and_then(|out| {
            File::create("/vagga/container/composer-list.txt")
            .and_then(|mut f| f.write_all(&out))
            .map_err(|e| format!("Error dumping composer package list: {}", e))
        }));

    Ok(())
}
<commit_msg>Optimize composer installation<commit_after>use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::Write;

use unshare::{Command};
use scan_dir::{self, ScanDir};
use regex::Regex;

use super::super::context::{Context};
use super::super::packages;
use super::generic::{run_command, capture_command};
use builder::error::StepError;
use builder::distrib::Distribution;
use builder::commands::generic::{command, run};
use builder::download;
use config::builders::{ComposerSettings, ComposerDepInfo};
use process_util::capture_stdout;
use file_util;

const DEFAULT_RUNTIME: &'static str = "/usr/bin/php";
const DEFAULT_INCLUDE_PATH: &'static str = ".:/usr/local/lib/composer";

const COMPOSER_HOME: &'static str = "/usr/local/lib/composer";
const COMPOSER_CACHE: &'static str = "/tmp/composer-cache";
const COMPOSER_VENDOR_DIR: &'static str = "/usr/local/lib/composer/vendor";
const COMPOSER_BIN_DIR: &'static str = "/usr/local/bin";
const COMPOSER_BOOTSTRAP: &'static str = "https://getcomposer.org/installer";
const COMPOSER_SELF_CACHE: &'static str = "/tmp/composer-self-cache";

impl Default for ComposerSettings {
    fn default() -> Self {
        ComposerSettings {
            install_runtime: true,
            install_dev: false,
            runtime_exe: None,
            include_path: None,
            keep_composer: false,
            vendor_dir: None,
        }
    }
}

fn scan_features(settings: &ComposerSettings)
    -> Vec<packages::Package>
{
    let mut res = vec!();
    res.push(packages::Https);
    res.push(packages::Composer);
    if settings.install_runtime {
        res.push(packages::Php);
        if settings.install_dev {
            res.push(packages::BuildEssential);
            res.push(packages::PhpDev)
        }
    }
    // Probably it's not it worth trying to figure out whether we need Git or Mercurial and it is
    // more likely that a php project is using Git, therefore it is reasonable to simply assume we
    // always need Git
    res.push(packages::Git);
    return res;
}

fn composer_cmd(ctx: &mut Context) -> Result<Command, StepError> {
    let runtime = ctx.composer_settings
        .runtime_exe
        .clone()
        .unwrap_or(DEFAULT_RUNTIME.to_owned());
    let mut cmd = try!(command(ctx, runtime));
    cmd.arg("/usr/local/bin/composer");
    cmd.arg("--no-interaction");
    Ok(cmd)
}

pub fn composer_install(distro: &mut Box<Distribution>, ctx: &mut Context,
    pkgs: &Vec<String>)
    -> Result<(), String>
{
    let features = scan_features(&ctx.composer_settings);
    try!(packages::ensure_packages(distro, ctx, &features));

    if pkgs.len() == 0 {
        return Ok(());
    }

    let mut cmd = try!(composer_cmd(ctx));
    cmd.args(&["global", "require", "--prefer-dist", "--update-no-dev"]);
    cmd.args(pkgs);
    try!(run(cmd));
    Ok(())
}

pub fn composer_dependencies(distro: &mut Box<Distribution>,
    ctx: &mut Context, info: &ComposerDepInfo)
    -> Result<(), StepError>
{
    let features = scan_features(&ctx.composer_settings);
    try!(packages::ensure_packages(distro, ctx, &features));

    let mut cmd = try!(composer_cmd(ctx));
    cmd.arg("install");
    if let Some(ref dir) = info.working_dir {
        cmd.arg(format!("--working-dir={}", dir));
    }
    if !info.dev { cmd.arg("--no-dev"); }
    if info.ignore_platform_reqs { cmd.arg("--ignore-platform-reqs"); }
    if info.no_autoloader { cmd.arg("--no_autoloader"); }
    if info.no_scripts { cmd.arg("--no-scripts"); }
    if info.no_plugins { cmd.arg("--no-plugins"); }
    if info.optimize_autoloader { cmd.arg("--optimize-autoloader"); }

    match info.prefer {
        Some(ref p) if p == "dist" => { cmd.arg("--prefer-dist"); },
        Some(ref p) if p == "source" => { cmd.arg("--prefer-source"); },
        Some(ref p) => return Err(From::from(format!(
            "Value of 'ComposerDependencies.prefer' must be either \
            'source' or 'dist', '{}' given", p
        ))),
        _ => {}
    }

    run(cmd)
}

pub fn configure(ctx: &mut Context) -> Result<(), String> {
    try!(ctx.add_cache_dir(Path::new(COMPOSER_CACHE),
                           "composer-cache".to_string()));

    try!(ctx.add_cache_dir(Path::new(COMPOSER_SELF_CACHE),
                           "composer-self-cache".to_owned()));

    let vendor_dir = ctx.composer_settings.vendor_dir.as_ref()
        .map(|p| p.to_string_lossy().into_owned())
        .unwrap_or_else(|| COMPOSER_VENDOR_DIR.to_owned());

    ctx.environ.insert("COMPOSER_VENDOR_DIR".to_owned(), vendor_dir);

    ctx.environ.insert("COMPOSER_HOME".to_owned(),
                       COMPOSER_HOME.to_owned());
    ctx.environ.insert("COMPOSER_BIN_DIR".to_owned(),
                       COMPOSER_BIN_DIR.to_owned());
    ctx.environ.insert("COMPOSER_CACHE_DIR".to_owned(),
                       COMPOSER_CACHE.to_owned());
    ctx.environ.insert("COMPOSER_ALLOW_SUPERUSER".to_owned(),
                       "1".to_owned());

    Ok(())
}

pub fn bootstrap(ctx: &mut Context) -> Result<(), String> {
    try_msg!(file_util::create_dir(COMPOSER_HOME, true),
        "Error creating composer home dir {d:?}: {err}", d=COMPOSER_HOME);

    let runtime_exe = ctx.composer_settings
        .runtime_exe
        .clone()
        .unwrap_or(DEFAULT_RUNTIME.to_owned());

    let cached_composer = format!("/vagga/root{}/composer.phar", COMPOSER_SELF_CACHE);
    if Path::new(&cached_composer).exists() {
        try!(update_composer(ctx, &runtime_exe));
    } else {
        try!(install_composer(ctx, &runtime_exe));
    }

    try!(file_util::copy(cached_composer, "/vagga/root/usr/local/bin/composer")
        .map_err(|e| format!("Error copying composer binary: {}", e)));

    if ctx.composer_settings.install_runtime {
        try!(setup_include_path(ctx));
    }

    Ok(())
}

fn update_composer(ctx: &mut Context, runtime: &str) -> Result<(), String> {
    let args = [
        runtime.to_owned(),
        format!("{}/composer.phar", COMPOSER_SELF_CACHE),
        "self-update".to_owned(),
        "--clean-backups".to_owned(),
    ];

    run_command(ctx, &args)
}

fn install_composer(ctx: &mut Context, runtime: &str) -> Result<(), String> {
    let composer_inst = try!(download::download_file(ctx, COMPOSER_BOOTSTRAP));
    try!(file_util::copy(&composer_inst, &Path::new("/vagga/root/tmp/composer-setup.php"))
        .map_err(|e| format!("Error copying composer installer: {}", e)));

    let args = [
        runtime.to_owned(),
        "/tmp/composer-setup.php".to_owned(),
        format!("--install-dir={}", COMPOSER_SELF_CACHE),
    ];

    run_command(ctx, &args)
}

fn setup_include_path(ctx: &mut Context) -> Result<(), String> {
    let vagga_ini_content = {
        let include_path = ctx.composer_settings
            .include_path.clone()
            .unwrap_or(DEFAULT_INCLUDE_PATH.to_owned());
        format!("include_path={}", include_path)
    };

    let conf_dirs = try_msg!(find_conf_dirs(),
                             "Error listing PHP configuration directories: {err}");

    for conf_d in conf_dirs.iter() {
        // create vagga.ini file
        try!(create_vagga_ini(&conf_d.join("vagga.ini"), &vagga_ini_content));
    }

    if !conf_dirs.is_empty() {
        // vagga.ini file(s) created, we're done here
        return Ok(())
    }

    // If we didn't find any conf.d, ask 'php --ini'
    let conf_d = try!(ask_php_for_conf_d(ctx));

    // create conf.d
    if !conf_d.exists() {
        try!(file_util::create_dir(&conf_d, true)
        .map_err(|e| format!("Error creating directory {:?}: {}", conf_d, e)));
    }
    // create vagga.ini file
    try!(create_vagga_ini(&conf_d, &vagga_ini_content));

    Ok(())
}

fn create_vagga_ini(location: &Path, content: &str) -> Result<(), String> {
    File::create(location)
        .and_then(|mut f| f.write_all(content.as_bytes()))
        .map_err(|e| format!("Error creating file {:?}: {}", location, e))
}

fn find_conf_dirs() -> Result<Vec<PathBuf>, scan_dir::Error> {
    ScanDir::dirs().skip_symlinks(true).read("/vagga/root/etc", |iter| {
        iter.filter(|&(_, ref name)| name.starts_with("php"))
        .flat_map(|(ref entry, _)| {
            ScanDir::dirs().read(entry.path(), |iter| {
                iter.filter(|&(ref entry, ref name)| {
                    name == "conf.d" ||
                    entry.path().join("conf.d").exists()
                })
                .map(|(ref entry, _)| {
                    let path = entry.path();
                    if path.ends_with("conf.d") { path }
                    else { path.join("conf.d") }
                })
                .collect()
            })
        })
        .collect()
    })
}

fn ask_php_for_conf_d(ctx: &mut Context) -> Result<PathBuf, String> {
    let runtime_exe = ctx.composer_settings
        .runtime_exe
        .clone()
        .unwrap_or(DEFAULT_RUNTIME.to_owned());

    let args = [runtime_exe, "--ini".to_owned()];
    let output = try!(capture_command(ctx, &args, &[])
        .and_then(|x| String::from_utf8(x)
            .map_err(|e| format!("Error parsing command output: {}", e)))
        .map_err(|e| format!("Error reading command output: {}", e)));

    // match any line that ends with /etc/php*/**/conf.d, get first result
    let re = Regex::new(r#"(?m).*?(/etc/php\d/.*?conf.d)$"#).expect("Invalid regex");

    let conf_d = try!(re.captures(&output)
        .and_then(|cap| cap.at(1))
        .ok_or("PHP configuration directory was not found".to_owned()));

    Ok(PathBuf::from(conf_d))
}

pub fn finish(ctx: &mut Context) -> Result<(), StepError> {
    try!(list_packages(ctx));
    if !ctx.composer_settings.keep_composer {
        try!(fs::remove_file(Path::new("/vagga/root/usr/local/bin/composer"))
            .map_err(|e| format!("Error removing '/usr/local/bin/composer': {}", e)));
    }

    Ok(())
}

fn list_packages(ctx: &mut Context) -> Result<(), StepError> {
    let mut cmd = try!(composer_cmd(ctx));
    cmd.arg("show");

    try!(capture_stdout(cmd)
        .and_then(|out| {
            File::create("/vagga/container/composer-list.txt")
            .and_then(|mut f| f.write_all(&out))
            .map_err(|e| format!("Error dumping composer package list: {}", e))
        }));

    Ok(())
}
<|endoftext|>